@team-agent/installer 0.5.64 → 0.5.66
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/Cargo.toml +5 -0
- package/crates/team-agent/src/cli/adapters.rs +6 -1
- package/crates/team-agent/src/cli/emit.rs +15 -0
- package/crates/team-agent/src/cli/mod.rs +9 -8
- package/crates/team-agent/src/cli/send/presentation.rs +1 -0
- package/crates/team-agent/src/cli/spec.rs +3 -0
- package/crates/team-agent/src/cli/tests/compile.rs +1 -1
- package/crates/team-agent/src/cli/tests/mod.rs +33 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
- package/crates/team-agent/src/cli/tests/status_send.rs +13 -3
- package/crates/team-agent/src/cli/tests/verb_validate.rs +1 -1
- package/crates/team-agent/src/cli/types.rs +7 -0
- package/crates/team-agent/src/compiler/tests.rs +23 -20
- package/crates/team-agent/src/compiler.rs +25 -5
- package/crates/team-agent/src/conpty/backend.rs +16 -0
- package/crates/team-agent/src/coordinator/health.rs +17 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +55 -2
- package/crates/team-agent/src/db/migration.rs +2 -1
- package/crates/team-agent/src/db/schema.rs +16 -8
- package/crates/team-agent/src/leader/lease.rs +1 -2
- package/crates/team-agent/src/leader/start.rs +175 -10
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +0 -2
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +7 -2
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +137 -2
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +1 -2
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +5 -0
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +39 -2
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +105 -48
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +0 -2
- package/crates/team-agent/src/lifecycle/launch.rs +117 -21
- package/crates/team-agent/src/lifecycle/restart/agent.rs +89 -3
- package/crates/team-agent/src/lifecycle/restart/common.rs +9 -12
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +59 -15
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +4 -4
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +24 -32
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +13 -11
- package/crates/team-agent/src/lifecycle/tests/core.rs +0 -18
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +17 -30
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +4 -4
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +11 -7
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests.rs +11 -7
- package/crates/team-agent/src/lifecycle/types.rs +0 -2
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +115 -22
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/messaging/delivery.rs +29 -0
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +12 -0
- package/crates/team-agent/src/messaging/mod.rs +2 -0
- package/crates/team-agent/src/messaging/results.rs +5 -0
- package/crates/team-agent/src/messaging/send.rs +9 -0
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
- package/crates/team-agent/src/messaging/tests/runtime.rs +32 -0
- package/crates/team-agent/src/messaging/types.rs +2 -0
- package/crates/team-agent/src/messaging/wait.rs +366 -0
- package/crates/team-agent/src/messaging/watchers.rs +153 -6
- package/crates/team-agent/src/model/spec.rs +129 -6
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
- package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
- package/crates/team-agent/src/provider/adapters/claude.rs +5 -2
- package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
- package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
- package/crates/team-agent/src/provider/bypass_flags.rs +80 -0
- package/crates/team-agent/src/provider/mod.rs +2 -0
- package/crates/team-agent/src/tmux_backend.rs +35 -12
- package/crates/team-agent/src/transport/test_support.rs +20 -0
- package/crates/team-agent/src/transport.rs +28 -11
- package/package.json +4 -4
- package/crates/team-agent/src/lifecycle/launch/approval.rs +0 -134
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
|
@@ -354,8 +354,31 @@ fn basic_schema_errors(spec: &Yaml) -> Vec<String> {
|
|
|
354
354
|
);
|
|
355
355
|
match spec.get("agents") {
|
|
356
356
|
Some(Yaml::List(agents)) if !agents.is_empty() => {
|
|
357
|
+
// 0.5.66 bypass 单源:收集缺/错 `dangerously_skip_permissions` 的角色清单,
|
|
358
|
+
// 循环后一次性报出,满足"缺字段错误消息含每个缺字段的角色 id 清单"硬要求。
|
|
359
|
+
let mut missing_bypass: Vec<String> = Vec::new();
|
|
360
|
+
let mut bad_type_bypass: Vec<String> = Vec::new();
|
|
357
361
|
for (idx, agent) in agents.iter().enumerate() {
|
|
358
362
|
check_agent(agent, &format!("/agents/{idx}"), &mut e);
|
|
363
|
+
match agent.get("dangerously_skip_permissions") {
|
|
364
|
+
None => missing_bypass.push(agent_label(agent, idx)),
|
|
365
|
+
Some(value) if !matches!(value, Yaml::Bool(_)) => {
|
|
366
|
+
bad_type_bypass.push(agent_label(agent, idx))
|
|
367
|
+
}
|
|
368
|
+
Some(_) => {}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if !missing_bypass.is_empty() {
|
|
372
|
+
e.push(format!(
|
|
373
|
+
"/agents/dangerously_skip_permissions: missing required field (role id(s): {}). This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed.",
|
|
374
|
+
missing_bypass.join(", ")
|
|
375
|
+
));
|
|
376
|
+
}
|
|
377
|
+
if !bad_type_bypass.is_empty() {
|
|
378
|
+
e.push(format!(
|
|
379
|
+
"/agents/dangerously_skip_permissions: must be a boolean (role id(s): {}). This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed.",
|
|
380
|
+
bad_type_bypass.join(", ")
|
|
381
|
+
));
|
|
359
382
|
}
|
|
360
383
|
}
|
|
361
384
|
_ => e.push("/agents: must be a non-empty list".to_string()),
|
|
@@ -384,7 +407,7 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
384
407
|
"working_directory",
|
|
385
408
|
"system_prompt",
|
|
386
409
|
"tools",
|
|
387
|
-
"
|
|
410
|
+
"dangerously_skip_permissions",
|
|
388
411
|
"preferred_for",
|
|
389
412
|
"avoid_for",
|
|
390
413
|
"output_contract",
|
|
@@ -397,7 +420,7 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
397
420
|
"working_directory",
|
|
398
421
|
"system_prompt",
|
|
399
422
|
"tools",
|
|
400
|
-
"
|
|
423
|
+
"dangerously_skip_permissions",
|
|
401
424
|
"preferred_for",
|
|
402
425
|
"avoid_for",
|
|
403
426
|
"output_contract",
|
|
@@ -407,6 +430,10 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
407
430
|
"profile",
|
|
408
431
|
"credential_ref",
|
|
409
432
|
"forked_from",
|
|
433
|
+
// 存量角色 md 的 `permission_mode` 是历史无效值(compiler 曾恒发 restricted)。
|
|
434
|
+
// 0.5.66 起不再被 compiler 消费;保留在 allowed 以免存量文件 fail-loud
|
|
435
|
+
// (§3.1 迁移时顺手删除,0.6.0 可收紧)。
|
|
436
|
+
"permission_mode",
|
|
410
437
|
// 0.4.x provider effort MVP step 3: per-agent effort override (resolved at compile).
|
|
411
438
|
"effort",
|
|
412
439
|
];
|
|
@@ -481,6 +508,15 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
481
508
|
}
|
|
482
509
|
}
|
|
483
510
|
|
|
511
|
+
/// 汇总错误里引用 agent 的可读标签:`<id>` 优先,退回 `<index> (role: <role>)`。
|
|
512
|
+
fn agent_label(agent: &Yaml, idx: usize) -> String {
|
|
513
|
+
agent
|
|
514
|
+
.get("id")
|
|
515
|
+
.and_then(Yaml::as_str)
|
|
516
|
+
.map(|id| format!("`{id}`"))
|
|
517
|
+
.unwrap_or_else(|| format!("agents[{idx}]"))
|
|
518
|
+
}
|
|
519
|
+
|
|
484
520
|
fn check_routing(routing: Option<&Yaml>, errors: &mut Vec<String>) {
|
|
485
521
|
check_keys_y(
|
|
486
522
|
routing,
|
|
@@ -564,7 +600,6 @@ fn check_runtime(runtime: Option<&Yaml>, errors: &mut Vec<String>) {
|
|
|
564
600
|
"max_active_agents",
|
|
565
601
|
"startup_order",
|
|
566
602
|
"display_backend",
|
|
567
|
-
"dangerous_auto_approve",
|
|
568
603
|
"auto_attach_leader",
|
|
569
604
|
"fast",
|
|
570
605
|
"tick_interval_sec",
|
|
@@ -588,9 +623,6 @@ fn check_runtime(runtime: Option<&Yaml>, errors: &mut Vec<String>) {
|
|
|
588
623
|
errors.push("/runtime/display_backend: invalid display backend".to_string());
|
|
589
624
|
}
|
|
590
625
|
}
|
|
591
|
-
if get("dangerous_auto_approve").is_some_and(|v| !matches!(v, Yaml::Bool(_))) {
|
|
592
|
-
errors.push("/runtime/dangerous_auto_approve: must be a boolean".to_string());
|
|
593
|
-
}
|
|
594
626
|
if get("auto_trust_own_workspace").is_some_and(|v| !matches!(v, Yaml::Bool(_))) {
|
|
595
627
|
errors.push("/runtime/auto_trust_own_workspace: must be a boolean".to_string());
|
|
596
628
|
}
|
|
@@ -1020,6 +1052,58 @@ mod tests {
|
|
|
1020
1052
|
);
|
|
1021
1053
|
}
|
|
1022
1054
|
|
|
1055
|
+
// 0.5.66 bypass 单源 §4.1:缺 `dangerously_skip_permissions` 字段 → 错误消息
|
|
1056
|
+
// 必须列出每个缺字段的角色 id + 用户可读的必填说明。
|
|
1057
|
+
#[test]
|
|
1058
|
+
fn test_missing_dangerously_field_reports_role_list() {
|
|
1059
|
+
// 从 fixture 文本里删掉第一个 agent 的字段,模拟该角色缺字段。
|
|
1060
|
+
let text = include_str!("testdata/team.spec.yaml");
|
|
1061
|
+
let without_first = text.replacen(" dangerously_skip_permissions: false\n", "", 1);
|
|
1062
|
+
assert_ne!(text, without_first, "fixture must contain the field");
|
|
1063
|
+
let spec = yaml::loads(&without_first).unwrap();
|
|
1064
|
+
let errors = all_spec_errors(&spec);
|
|
1065
|
+
let missing: Vec<&String> = errors
|
|
1066
|
+
.iter()
|
|
1067
|
+
.filter(|m| m.starts_with("/agents/dangerously_skip_permissions: missing"))
|
|
1068
|
+
.collect();
|
|
1069
|
+
assert_eq!(missing.len(), 1, "exactly one missing-field error, got: {errors:#?}");
|
|
1070
|
+
assert!(
|
|
1071
|
+
missing[0].contains("`codex_implementer`"),
|
|
1072
|
+
"must name the role id, got: {}",
|
|
1073
|
+
missing[0]
|
|
1074
|
+
);
|
|
1075
|
+
assert!(
|
|
1076
|
+
missing[0].contains("This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed."),
|
|
1077
|
+
"must carry the user-facing required-field message, got: {}",
|
|
1078
|
+
missing[0]
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// 0.5.66 bypass 单源 §4.1:非 bool 值 → fail-loud 类型错,同样带角色清单 + 硬串。
|
|
1083
|
+
#[test]
|
|
1084
|
+
fn test_invalid_dangerously_type_rejected() {
|
|
1085
|
+
let text = include_str!("testdata/team.spec.yaml");
|
|
1086
|
+
let bad = text.replacen(" dangerously_skip_permissions: false\n", " dangerously_skip_permissions: \"yes\"\n", 1);
|
|
1087
|
+
assert_ne!(text, bad, "fixture must contain the field");
|
|
1088
|
+
let spec = yaml::loads(&bad).unwrap();
|
|
1089
|
+
let errors = all_spec_errors(&spec);
|
|
1090
|
+
let type_errs: Vec<&String> = errors
|
|
1091
|
+
.iter()
|
|
1092
|
+
.filter(|m| m.starts_with("/agents/dangerously_skip_permissions: must be a boolean"))
|
|
1093
|
+
.collect();
|
|
1094
|
+
assert_eq!(type_errs.len(), 1, "exactly one type error, got: {errors:#?}");
|
|
1095
|
+
assert!(
|
|
1096
|
+
type_errs[0].contains("`codex_implementer`"),
|
|
1097
|
+
"must name the role id, got: {}",
|
|
1098
|
+
type_errs[0]
|
|
1099
|
+
);
|
|
1100
|
+
assert!(
|
|
1101
|
+
type_errs[0].contains("This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed."),
|
|
1102
|
+
"must carry the user-facing required-field message, got: {}",
|
|
1103
|
+
type_errs[0]
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1023
1107
|
#[test]
|
|
1024
1108
|
fn empty_spec_matches_python_golden() {
|
|
1025
1109
|
let spec = yaml::loads("{}").unwrap();
|
|
@@ -1050,4 +1134,43 @@ mod tests {
|
|
|
1050
1134
|
]
|
|
1051
1135
|
);
|
|
1052
1136
|
}
|
|
1137
|
+
|
|
1138
|
+
// 0.5.66 bypass 单源 §4.1:7 触发面共用同一 spec 校验层——compile_team 是它们的共同
|
|
1139
|
+
// 必经点(quick-start/restart/add/fork 都调用),缺字段一律 fail-loud。此处验证
|
|
1140
|
+
// compile 级校验确实 fail-loud,即各触发面(经 compile_team)都被覆盖。
|
|
1141
|
+
#[test]
|
|
1142
|
+
fn test_all_7_triggers_run_field_check() {
|
|
1143
|
+
// compile_team 是 7 触发面的 spec 校验必经点:
|
|
1144
|
+
// quick-start(quick_start.rs:233 compile_team)
|
|
1145
|
+
// restart(rebuild.rs:3245 rebuild_runtime_spec_from_roles → compile_team)
|
|
1146
|
+
// add-agent(add_agent.rs:331 compile_role_agent)
|
|
1147
|
+
// clone-agent(clone_agent.rs:62 → add_agent)
|
|
1148
|
+
// fork-agent(fork_agent.rs:96 validate_spec)
|
|
1149
|
+
// start-agent / reset-agent(操作已编译 spec,团队创建时经 compile_team 校验)
|
|
1150
|
+
// 验证 compile_team 对缺字段的角色文档 fail-loud。
|
|
1151
|
+
let team = std::env::temp_dir().join(format!(
|
|
1152
|
+
"ta-spec-7trigger-{}",
|
|
1153
|
+
std::process::id()
|
|
1154
|
+
));
|
|
1155
|
+
let _ = std::fs::remove_dir_all(&team);
|
|
1156
|
+
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
1157
|
+
std::fs::write(
|
|
1158
|
+
team.join("TEAM.md"),
|
|
1159
|
+
"---\nname: t7\nobjective: 7 trigger.\nprovider: codex\n---\n\nteam.\n",
|
|
1160
|
+
)
|
|
1161
|
+
.unwrap();
|
|
1162
|
+
// 角色文档故意缺 dangerously_skip_permissions
|
|
1163
|
+
std::fs::write(
|
|
1164
|
+
team.join("agents").join("w.md"),
|
|
1165
|
+
"---\nname: w\nrole: Worker\nprovider: codex\ntools:\n - mcp_team\n---\n\nw.\n",
|
|
1166
|
+
)
|
|
1167
|
+
.unwrap();
|
|
1168
|
+
let err = crate::compiler::compile_team(&team).unwrap_err().to_string();
|
|
1169
|
+
assert!(
|
|
1170
|
+
err.contains("missing front matter field dangerously_skip_permissions")
|
|
1171
|
+
&& err.contains("This field must be declared explicitly; it controls whether the agent launches with permission prompts bypassed."),
|
|
1172
|
+
"compile_team must fail-loud on missing field (shared by all 7 triggers); got {err}"
|
|
1173
|
+
);
|
|
1174
|
+
let _ = std::fs::remove_dir_all(&team);
|
|
1175
|
+
}
|
|
1053
1176
|
}
|
|
@@ -39,6 +39,7 @@ agents:
|
|
|
39
39
|
- "provider_builtin"
|
|
40
40
|
- "banana"
|
|
41
41
|
permission_mode: "restricted"
|
|
42
|
+
dangerously_skip_permissions: false
|
|
42
43
|
preferred_for:
|
|
43
44
|
- "implementation"
|
|
44
45
|
- "bug_fix"
|
|
@@ -69,6 +70,7 @@ agents:
|
|
|
69
70
|
- "mcp_team"
|
|
70
71
|
- "provider_builtin"
|
|
71
72
|
permission_mode: "restricted"
|
|
73
|
+
dangerously_skip_permissions: false
|
|
72
74
|
preferred_for:
|
|
73
75
|
- "research"
|
|
74
76
|
- "architecture"
|
|
@@ -99,6 +101,7 @@ agents:
|
|
|
99
101
|
- "mcp_team"
|
|
100
102
|
- "provider_builtin"
|
|
101
103
|
permission_mode: "restricted"
|
|
104
|
+
dangerously_skip_permissions: false
|
|
102
105
|
preferred_for:
|
|
103
106
|
- "review"
|
|
104
107
|
- "risk_check"
|
|
@@ -141,7 +144,6 @@ runtime:
|
|
|
141
144
|
session_name: "teamspec-full-example"
|
|
142
145
|
auto_launch: true
|
|
143
146
|
require_user_approval_before_launch: true
|
|
144
|
-
dangerous_auto_approve: false
|
|
145
147
|
max_active_agents: 3
|
|
146
148
|
startup_order:
|
|
147
149
|
- "codex_implementer"
|
|
@@ -38,6 +38,7 @@ agents:
|
|
|
38
38
|
- "mcp_team"
|
|
39
39
|
- "provider_builtin"
|
|
40
40
|
permission_mode: "restricted"
|
|
41
|
+
dangerously_skip_permissions: false
|
|
41
42
|
preferred_for:
|
|
42
43
|
- "implementation"
|
|
43
44
|
- "bug_fix"
|
|
@@ -68,6 +69,7 @@ agents:
|
|
|
68
69
|
- "mcp_team"
|
|
69
70
|
- "provider_builtin"
|
|
70
71
|
permission_mode: "restricted"
|
|
72
|
+
dangerously_skip_permissions: false
|
|
71
73
|
preferred_for:
|
|
72
74
|
- "research"
|
|
73
75
|
- "architecture"
|
|
@@ -98,6 +100,7 @@ agents:
|
|
|
98
100
|
- "mcp_team"
|
|
99
101
|
- "provider_builtin"
|
|
100
102
|
permission_mode: "restricted"
|
|
103
|
+
dangerously_skip_permissions: false
|
|
101
104
|
preferred_for:
|
|
102
105
|
- "review"
|
|
103
106
|
- "risk_check"
|
|
@@ -140,7 +143,6 @@ runtime:
|
|
|
140
143
|
session_name: "teamspec-full-example"
|
|
141
144
|
auto_launch: true
|
|
142
145
|
require_user_approval_before_launch: true
|
|
143
|
-
dangerous_auto_approve: false
|
|
144
146
|
max_active_agents: 3
|
|
145
147
|
startup_order:
|
|
146
148
|
- "codex_implementer"
|
|
@@ -38,6 +38,7 @@ agents:
|
|
|
38
38
|
- mcp_team
|
|
39
39
|
- provider_builtin
|
|
40
40
|
permission_mode: restricted
|
|
41
|
+
dangerously_skip_permissions: false
|
|
41
42
|
preferred_for:
|
|
42
43
|
- implementation
|
|
43
44
|
- bug_fix
|
|
@@ -68,6 +69,7 @@ agents:
|
|
|
68
69
|
- mcp_team
|
|
69
70
|
- provider_builtin
|
|
70
71
|
permission_mode: restricted
|
|
72
|
+
dangerously_skip_permissions: false
|
|
71
73
|
preferred_for:
|
|
72
74
|
- research
|
|
73
75
|
- architecture
|
|
@@ -98,6 +100,7 @@ agents:
|
|
|
98
100
|
- mcp_team
|
|
99
101
|
- provider_builtin
|
|
100
102
|
permission_mode: restricted
|
|
103
|
+
dangerously_skip_permissions: false
|
|
101
104
|
preferred_for:
|
|
102
105
|
- review
|
|
103
106
|
- risk_check
|
|
@@ -140,7 +143,6 @@ runtime:
|
|
|
140
143
|
session_name: teamspec-full-example
|
|
141
144
|
auto_launch: true
|
|
142
145
|
require_user_approval_before_launch: true
|
|
143
|
-
dangerous_auto_approve: false
|
|
144
146
|
max_active_agents: 3
|
|
145
147
|
startup_order:
|
|
146
148
|
- codex_implementer
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
//! resolver (`claude_context_model`) stay in `adapter.rs`; capture scanning
|
|
9
9
|
//! helpers now live under `provider/session_scan/claude.rs`.
|
|
10
10
|
|
|
11
|
-
use crate::model::enums::AuthMode;
|
|
11
|
+
use crate::model::enums::{AuthMode, Provider};
|
|
12
12
|
use crate::provider::adapter::{next_session_token, prompt_needs_native_mcp, BasicProviderAdapter};
|
|
13
13
|
use crate::provider::{McpConfig, ProviderAdapter, ProviderError};
|
|
14
14
|
|
|
@@ -49,7 +49,10 @@ pub(crate) fn claude_base_command(
|
|
|
49
49
|
) -> Result<Vec<String>, ProviderError> {
|
|
50
50
|
let mut argv = vec!["claude".to_string()];
|
|
51
51
|
if claude_dangerous_auto_approve(tools) {
|
|
52
|
-
|
|
52
|
+
// 0.5.66 bypass 单源:flag 由 provider_bypass_flag 表供给(单源)。
|
|
53
|
+
let flag = crate::provider::bypass_flags::provider_bypass_flag(Provider::Claude)
|
|
54
|
+
.expect("claude provider must define a bypass flag");
|
|
55
|
+
argv.push(flag.to_string());
|
|
53
56
|
} else {
|
|
54
57
|
argv.push("--permission-mode".to_string());
|
|
55
58
|
argv.push("default".to_string());
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//! `json_inline` stays in `adapter.rs` because it is also used by other
|
|
7
7
|
//! sites; this file reaches it via `super::*`.
|
|
8
8
|
|
|
9
|
-
use crate::model::enums::AuthMode;
|
|
9
|
+
use crate::model::enums::{AuthMode, Provider};
|
|
10
10
|
use crate::provider::adapter::json_inline;
|
|
11
11
|
use crate::provider::{McpConfig, ProviderCommandOverrides};
|
|
12
12
|
|
|
@@ -41,7 +41,10 @@ pub(crate) fn codex_base_command(
|
|
|
41
41
|
argv.push(profile.to_string());
|
|
42
42
|
}
|
|
43
43
|
if codex_dangerous_auto_approve(tools) {
|
|
44
|
-
|
|
44
|
+
// 0.5.66 bypass 单源:flag 由 provider_bypass_flag 表供给。
|
|
45
|
+
let flag = crate::provider::bypass_flags::provider_bypass_flag(Provider::Codex)
|
|
46
|
+
.expect("codex provider must define a bypass flag");
|
|
47
|
+
argv.push(flag.to_string());
|
|
45
48
|
} else {
|
|
46
49
|
argv.push("--sandbox".to_string());
|
|
47
50
|
argv.push(codex_sandbox_mode(tools).to_string());
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//! translation. Auth hint (`copilot_auth_hint`) stays in `adapter.rs`;
|
|
8
8
|
//! session-store scanning lives under `provider/session_scan/copilot.rs`.
|
|
9
9
|
|
|
10
|
-
use crate::model::enums::AuthMode;
|
|
10
|
+
use crate::model::enums::{AuthMode, Provider};
|
|
11
11
|
use crate::provider::McpConfig;
|
|
12
12
|
|
|
13
13
|
pub(crate) fn copilot_base_command(
|
|
@@ -29,7 +29,10 @@ pub(crate) fn copilot_base_command(
|
|
|
29
29
|
"--disable-builtin-mcps".to_string(),
|
|
30
30
|
];
|
|
31
31
|
if copilot_dangerous_auto_approve(tools) {
|
|
32
|
-
|
|
32
|
+
// 0.5.66 bypass 单源:flag 由 provider_bypass_flag 表供给。
|
|
33
|
+
let flag = crate::provider::bypass_flags::provider_bypass_flag(Provider::Copilot)
|
|
34
|
+
.expect("copilot provider must define a bypass flag");
|
|
35
|
+
argv.push(flag.to_string());
|
|
33
36
|
} else {
|
|
34
37
|
for flag in copilot_permission_flags(tools) {
|
|
35
38
|
argv.push(flag);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//!
|
|
2
|
+
//! 0.5.66 bypass 单源统一 — provider → bypass argv flag 配置表。
|
|
3
|
+
//!
|
|
4
|
+
//! **唯一权威**:角色 md 的 `dangerously_skip_permissions: true` 决定是否加 bypass flag;
|
|
5
|
+
//! 本文件只回答"某个 provider 的 bypass 命令行参数是什么"。
|
|
6
|
+
//!
|
|
7
|
+
//! 数据源自旧 `lifecycle/launch/approval.rs::dangerous_leader_flags()` 表迁来:
|
|
8
|
+
//! - claude/claude_code → `--dangerously-skip-permissions`
|
|
9
|
+
//! - codex → `--dangerously-bypass-approvals-and-sandbox`
|
|
10
|
+
//! - copilot → `--allow-all`(0.3.27 P1 E54 symptom 2 起)
|
|
11
|
+
//!
|
|
12
|
+
//! 未定义(返回 None)的 provider:调用侧必须 fail-loud(见
|
|
13
|
+
//! `resolved_tool_strings_for_command`),不得静默 fallback。
|
|
14
|
+
|
|
15
|
+
use crate::model::enums::Provider;
|
|
16
|
+
|
|
17
|
+
/// 某 provider 的 bypass argv flag;未定义 → `None`。
|
|
18
|
+
pub(crate) fn provider_bypass_flag(provider: Provider) -> Option<&'static str> {
|
|
19
|
+
match provider {
|
|
20
|
+
Provider::Claude | Provider::ClaudeCode => Some("--dangerously-skip-permissions"),
|
|
21
|
+
Provider::Codex => Some("--dangerously-bypass-approvals-and-sandbox"),
|
|
22
|
+
Provider::Copilot => Some("--allow-all"),
|
|
23
|
+
// TODO: 查 gemini_cli 的等价 bypass 参数;未定义前 fail-loud。
|
|
24
|
+
Provider::GeminiCli => None,
|
|
25
|
+
Provider::Fake => None,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// 0.5.66 §3.2 跨 workspace 兼容警示用:扫 argv tokens 找已知 bypass flag。
|
|
30
|
+
/// **只做检测,不做行为决策**(0.6.0 删)。
|
|
31
|
+
///
|
|
32
|
+
/// TODO(0.6.0): remove
|
|
33
|
+
pub(crate) fn detect_bypass_flag_in_argv(argv_tokens: &[String]) -> Option<&'static str> {
|
|
34
|
+
for token in argv_tokens {
|
|
35
|
+
if token == "--dangerously-skip-permissions"
|
|
36
|
+
|| token == "--dangerously-skip-permission"
|
|
37
|
+
{
|
|
38
|
+
return Some("--dangerously-skip-permissions");
|
|
39
|
+
}
|
|
40
|
+
if token == "--dangerously-bypass-approvals-and-sandbox" {
|
|
41
|
+
return Some("--dangerously-bypass-approvals-and-sandbox");
|
|
42
|
+
}
|
|
43
|
+
if token == "--allow-all" || token == "--yolo" {
|
|
44
|
+
return Some("--allow-all");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
None
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[cfg(test)]
|
|
51
|
+
mod tests {
|
|
52
|
+
use super::*;
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn claude_family_bypass_flag() {
|
|
56
|
+
assert_eq!(
|
|
57
|
+
provider_bypass_flag(Provider::Claude),
|
|
58
|
+
Some("--dangerously-skip-permissions")
|
|
59
|
+
);
|
|
60
|
+
assert_eq!(
|
|
61
|
+
provider_bypass_flag(Provider::ClaudeCode),
|
|
62
|
+
Some("--dangerously-skip-permissions")
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[test]
|
|
67
|
+
fn codex_and_copilot_bypass_flags() {
|
|
68
|
+
assert_eq!(
|
|
69
|
+
provider_bypass_flag(Provider::Codex),
|
|
70
|
+
Some("--dangerously-bypass-approvals-and-sandbox")
|
|
71
|
+
);
|
|
72
|
+
assert_eq!(provider_bypass_flag(Provider::Copilot), Some("--allow-all"));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[test]
|
|
76
|
+
fn undefined_providers_return_none() {
|
|
77
|
+
assert_eq!(provider_bypass_flag(Provider::GeminiCli), None);
|
|
78
|
+
assert_eq!(provider_bypass_flag(Provider::Fake), None);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
// 复用 model::enums 的 Provider/AuthMode,并 re-export(下游惯于 `provider::Provider` 取用)。
|
|
33
33
|
pub use crate::model::enums::{AuthMode, Provider};
|
|
34
34
|
|
|
35
|
+
/// 0.5.66 bypass 单源:provider → bypass argv flag 配置表(唯一权威)。
|
|
36
|
+
pub(crate) mod bypass_flags;
|
|
35
37
|
pub mod adapter;
|
|
36
38
|
/// 0.4.x decoupling step 2: per-provider command builders + permission/auth
|
|
37
39
|
/// helpers split out of `adapter.rs`. Pure extraction — behavior unchanged.
|
|
@@ -1723,8 +1723,8 @@ pub fn worker_provider_exit_marker(provider_label: &str) -> String {
|
|
|
1723
1723
|
/// as a CHILD of a long-lived shell so provider exit does NOT collapse the
|
|
1724
1724
|
/// worker pane (which under upstream tmux 3.6a private-server bugs can
|
|
1725
1725
|
/// cascade into whole-server death). When the provider exits, the worker
|
|
1726
|
-
/// pane
|
|
1727
|
-
///
|
|
1726
|
+
/// pane remains alive with an explicit worker exit marker, then runs an
|
|
1727
|
+
/// inert `sh` tail that does not read terminal input.
|
|
1728
1728
|
pub fn worker_shell_wrapper_command(
|
|
1729
1729
|
argv: &[String],
|
|
1730
1730
|
cwd: &Path,
|
|
@@ -1758,23 +1758,21 @@ pub fn worker_shell_wrapper_command(
|
|
|
1758
1758
|
worker_provider_exit_marker(provider_label)
|
|
1759
1759
|
)));
|
|
1760
1760
|
parts.push("\"$rc\";".to_string());
|
|
1761
|
-
parts.push(
|
|
1762
|
-
parts.push("\"${SHELL:-/bin/zsh}\"".to_string());
|
|
1763
|
-
parts.push("-l".to_string());
|
|
1761
|
+
parts.push(inert_pane_tail_command());
|
|
1764
1762
|
parts.join(" ")
|
|
1765
1763
|
}
|
|
1766
1764
|
|
|
1767
1765
|
/// 0.4.x (CR C-2): leader shell wrapper — provider runs as a CHILD of a
|
|
1768
1766
|
/// long-lived shell, not as the pane's primary process. When the provider
|
|
1769
|
-
/// exits, the pane
|
|
1770
|
-
///
|
|
1767
|
+
/// exits, the pane remains alive with an explicit exit marker, then runs an
|
|
1768
|
+
/// inert `sh` tail that does not read terminal input.
|
|
1771
1769
|
///
|
|
1772
1770
|
/// Four required envelope sections (CR C-2):
|
|
1773
1771
|
/// 1. cd <cwd> — same as `shell_command`
|
|
1774
1772
|
/// 2. unset <KEY> ... — provider env_unset block
|
|
1775
1773
|
/// 3. KEY=val ... <provider> — env exports + provider invocation
|
|
1776
1774
|
/// (NO `exec` — runs as child)
|
|
1777
|
-
/// 4. printf exit marker; exec
|
|
1775
|
+
/// 4. printf exit marker; exec inert sh tail
|
|
1778
1776
|
///
|
|
1779
1777
|
/// `provider_label` is a human-readable provider name (e.g. "claude",
|
|
1780
1778
|
/// "codex") embedded in the exit marker for diagnostics.
|
|
@@ -1809,7 +1807,7 @@ pub fn leader_shell_wrapper_command(
|
|
|
1809
1807
|
}
|
|
1810
1808
|
parts.extend(argv.iter().map(|arg| shell_quote(arg)));
|
|
1811
1809
|
parts.push(";".to_string());
|
|
1812
|
-
// 4. exit marker +
|
|
1810
|
+
// 4. exit marker + inert shell tail
|
|
1813
1811
|
parts.push("rc=$?;".to_string());
|
|
1814
1812
|
parts.push("printf".to_string());
|
|
1815
1813
|
// CR R6: marker text comes from single-source `leader_provider_exit_marker`.
|
|
@@ -1818,12 +1816,18 @@ pub fn leader_shell_wrapper_command(
|
|
|
1818
1816
|
leader_provider_exit_marker(provider_label)
|
|
1819
1817
|
)));
|
|
1820
1818
|
parts.push("\"$rc\";".to_string());
|
|
1821
|
-
parts.push(
|
|
1822
|
-
parts.push("\"${SHELL:-/bin/zsh}\"".to_string());
|
|
1823
|
-
parts.push("-l".to_string());
|
|
1819
|
+
parts.push(inert_pane_tail_command());
|
|
1824
1820
|
parts.join(" ")
|
|
1825
1821
|
}
|
|
1826
1822
|
|
|
1823
|
+
fn inert_pane_tail_command() -> String {
|
|
1824
|
+
// `sh` is deliberate: provider-exit health checks use the shell basename
|
|
1825
|
+
// to reach the exit-marker branch. `sh -c` does not read commands from
|
|
1826
|
+
// stdin; disabling echo also prevents input from appearing accepted.
|
|
1827
|
+
let script = r#"trap '' INT QUIT; stty -echo 2>/dev/null; printf "%s\n" "[team-agent] Provider exited; this pane no longer accepts input. Restart from another pane with the appropriate team-agent start command."; while :; do sleep 3600 & wait "$!"; done"#;
|
|
1828
|
+
format!("exec /bin/sh -c {}", shell_quote(script))
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1827
1831
|
fn shell_quote(raw: &str) -> String {
|
|
1828
1832
|
if raw.is_empty() {
|
|
1829
1833
|
return "''".to_string();
|
|
@@ -2553,6 +2557,25 @@ impl Transport for TmuxBackend {
|
|
|
2553
2557
|
Ok(SetEnvOutcome::Applied)
|
|
2554
2558
|
}
|
|
2555
2559
|
|
|
2560
|
+
fn set_window_option(
|
|
2561
|
+
&self,
|
|
2562
|
+
session: &SessionName,
|
|
2563
|
+
window: &WindowName,
|
|
2564
|
+
option: &str,
|
|
2565
|
+
value: &str,
|
|
2566
|
+
) -> Result<(), TransportError> {
|
|
2567
|
+
let target = format!("{}:{}", session.as_str(), window.as_str());
|
|
2568
|
+
let argv = vec![
|
|
2569
|
+
"tmux".to_string(),
|
|
2570
|
+
"set-window-option".to_string(),
|
|
2571
|
+
"-t".to_string(),
|
|
2572
|
+
target,
|
|
2573
|
+
option.to_string(),
|
|
2574
|
+
value.to_string(),
|
|
2575
|
+
];
|
|
2576
|
+
self.run_ok(&argv)
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2556
2579
|
fn kill_server(&self) -> Result<(), TransportError> {
|
|
2557
2580
|
TmuxBackend::kill_server(self);
|
|
2558
2581
|
Ok(())
|
|
@@ -35,6 +35,7 @@ struct OfflineState {
|
|
|
35
35
|
calls: Vec<&'static str>,
|
|
36
36
|
spawns: Vec<SpawnRecord>,
|
|
37
37
|
pane_titles: Vec<(String, String, String, String)>,
|
|
38
|
+
window_options: Vec<(String, String)>,
|
|
38
39
|
inject_targets: Vec<Target>,
|
|
39
40
|
inject_payloads: Vec<String>,
|
|
40
41
|
tmux_endpoint: Option<String>,
|
|
@@ -70,6 +71,7 @@ impl Default for OfflineState {
|
|
|
70
71
|
calls: Vec::new(),
|
|
71
72
|
spawns: Vec::new(),
|
|
72
73
|
pane_titles: Vec::new(),
|
|
74
|
+
window_options: Vec::new(),
|
|
73
75
|
inject_targets: Vec::new(),
|
|
74
76
|
inject_payloads: Vec::new(),
|
|
75
77
|
tmux_endpoint: None,
|
|
@@ -267,6 +269,10 @@ impl OfflineTransport {
|
|
|
267
269
|
self.with_state(|state| state.pane_titles.clone())
|
|
268
270
|
}
|
|
269
271
|
|
|
272
|
+
pub fn window_options(&self) -> Vec<(String, String)> {
|
|
273
|
+
self.with_state(|state| state.window_options.clone())
|
|
274
|
+
}
|
|
275
|
+
|
|
270
276
|
pub fn inject_targets(&self) -> Vec<Target> {
|
|
271
277
|
self.with_state(|state| state.inject_targets.clone())
|
|
272
278
|
}
|
|
@@ -555,6 +561,20 @@ impl Transport for OfflineTransport {
|
|
|
555
561
|
Ok(SetEnvOutcome::Applied)
|
|
556
562
|
}
|
|
557
563
|
|
|
564
|
+
fn set_window_option(
|
|
565
|
+
&self,
|
|
566
|
+
_session: &SessionName,
|
|
567
|
+
_window: &WindowName,
|
|
568
|
+
option: &str,
|
|
569
|
+
value: &str,
|
|
570
|
+
) -> Result<(), TransportError> {
|
|
571
|
+
self.with_state(|state| {
|
|
572
|
+
state.calls.push("set_window_option");
|
|
573
|
+
state.window_options.push((option.to_string(), value.to_string()));
|
|
574
|
+
});
|
|
575
|
+
Ok(())
|
|
576
|
+
}
|
|
577
|
+
|
|
558
578
|
fn kill_session(&self, session: &SessionName) -> Result<(), TransportError> {
|
|
559
579
|
self.with_state(|state| {
|
|
560
580
|
state.calls.push("kill_session");
|
|
@@ -613,11 +613,11 @@ pub trait Transport: Send + Sync {
|
|
|
613
613
|
/// variant that reuses the leader-wrapper mechanism so worker provider
|
|
614
614
|
/// exit does not collapse the pane into `[exited]` (which under
|
|
615
615
|
/// upstream tmux 3.6a private-server bugs can cascade into server
|
|
616
|
-
/// death). When the provider exits, the worker pane
|
|
617
|
-
///
|
|
618
|
-
///
|
|
619
|
-
///
|
|
620
|
-
///
|
|
616
|
+
/// death). When the provider exits, the worker pane keeps an explicit
|
|
617
|
+
/// worker exit marker and runs the inert `/bin/sh -c` tail: it ignores
|
|
618
|
+
/// INT/QUIT and does not read pane stdin. Default falls back to plain
|
|
619
|
+
/// `spawn_first_with_env_unset` for backends that have no shell layer
|
|
620
|
+
/// (test-only `OfflineTransport`).
|
|
621
621
|
fn spawn_first_with_worker_shell_wrapper(
|
|
622
622
|
&self,
|
|
623
623
|
session: &SessionName,
|
|
@@ -650,12 +650,12 @@ pub trait Transport: Send + Sync {
|
|
|
650
650
|
/// 0.4.x (CR C-2): leader-specific spawn variant. Instead of `exec <cmd>`
|
|
651
651
|
/// (which makes the provider the pane's primary process and turns the
|
|
652
652
|
/// pane into `[exited]` when the provider exits), build a shell line
|
|
653
|
-
/// that runs the provider as a CHILD of a long-lived shell
|
|
654
|
-
///
|
|
655
|
-
///
|
|
656
|
-
///
|
|
657
|
-
/// `
|
|
658
|
-
///
|
|
653
|
+
/// that runs the provider as a CHILD of a long-lived shell. When the
|
|
654
|
+
/// provider exits, the wrapper records the exit code and marker, then
|
|
655
|
+
/// execs an inert `/bin/sh -c` tail that ignores INT/QUIT and does not
|
|
656
|
+
/// read pane stdin. Default falls back to plain
|
|
657
|
+
/// `spawn_first_with_env_unset` for backends that have no shell layer
|
|
658
|
+
/// (test-only `OfflineTransport`).
|
|
659
659
|
fn spawn_first_with_leader_shell_wrapper(
|
|
660
660
|
&self,
|
|
661
661
|
session: &SessionName,
|
|
@@ -762,6 +762,23 @@ pub trait Transport: Send + Sync {
|
|
|
762
762
|
value: &str,
|
|
763
763
|
) -> Result<SetEnvOutcome, TransportError>;
|
|
764
764
|
|
|
765
|
+
/// A-55 (window rename race): set a tmux per-window option (e.g.
|
|
766
|
+
/// `allow-rename off`, `automatic-rename off`) on an existing window.
|
|
767
|
+
/// tmux=`set-window-option -t SESSION:WINDOW`; non-tmux backends refuse
|
|
768
|
+
/// via the default (typed unsupported).
|
|
769
|
+
fn set_window_option(
|
|
770
|
+
&self,
|
|
771
|
+
_session: &SessionName,
|
|
772
|
+
_window: &WindowName,
|
|
773
|
+
_option: &str,
|
|
774
|
+
_value: &str,
|
|
775
|
+
) -> Result<(), TransportError> {
|
|
776
|
+
Err(TransportError::MuxUnavailable {
|
|
777
|
+
backend: self.kind(),
|
|
778
|
+
detail: "set-window-option is tmux-only".to_string(),
|
|
779
|
+
})
|
|
780
|
+
}
|
|
781
|
+
|
|
765
782
|
// —— LIFECYCLE(SL)——
|
|
766
783
|
|
|
767
784
|
fn kill_server(&self) -> Result<(), TransportError> {
|