@team-agent/installer 0.5.66 → 0.5.68
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 +8 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -0
- package/crates/team-agent/src/cli/adapters.rs +5 -0
- package/crates/team-agent/src/cli/diagnose.rs +82 -0
- package/crates/team-agent/src/cli/emit.rs +3 -3
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +21 -7
- package/crates/team-agent/src/cli/leaders.rs +125 -1
- package/crates/team-agent/src/cli/mod.rs +20 -0
- package/crates/team-agent/src/cli/send/presentation.rs +7 -1
- package/crates/team-agent/src/cli/spec.rs +2 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
- package/crates/team-agent/src/cli/tests/base.rs +26 -3
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler.rs +59 -0
- package/crates/team-agent/src/coordinator/backoff.rs +55 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
- package/crates/team-agent/src/coordinator/health.rs +173 -0
- package/crates/team-agent/src/coordinator/mod.rs +31 -0
- package/crates/team-agent/src/coordinator/orphan.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
- package/crates/team-agent/src/coordinator/tick.rs +133 -22
- package/crates/team-agent/src/coordinator/types.rs +49 -0
- package/crates/team-agent/src/db/message_store.rs +39 -5
- package/crates/team-agent/src/layout/worker_env.rs +20 -3
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
- package/crates/team-agent/src/leader/registry.rs +65 -0
- package/crates/team-agent/src/leader/start.rs +920 -63
- package/crates/team-agent/src/lifecycle/display.rs +56 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -12
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -0
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
- package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
- package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +99 -29
- package/crates/team-agent/src/lifecycle/lock.rs +42 -1
- package/crates/team-agent/src/lifecycle/mod.rs +11 -0
- package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
- package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
- package/crates/team-agent/src/lifecycle/restart.rs +29 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
- package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
- package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +23 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
- package/crates/team-agent/src/mcp_server/tests.rs +196 -2
- package/crates/team-agent/src/messaging/delivery.rs +405 -40
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
- package/crates/team-agent/src/messaging/results.rs +4 -0
- package/crates/team-agent/src/messaging/send.rs +34 -0
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
- package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/types.rs +4 -1
- package/crates/team-agent/src/model/enums.rs +14 -5
- package/crates/team-agent/src/model/permissions.rs +3 -0
- package/crates/team-agent/src/os_probe.rs +73 -2
- package/crates/team-agent/src/provider/adapter.rs +231 -4
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
- package/crates/team-agent/src/provider/classify.rs +4 -2
- package/crates/team-agent/src/provider/faults.rs +2 -1
- package/crates/team-agent/src/provider/mod.rs +11 -0
- package/crates/team-agent/src/provider/session/capture.rs +158 -20
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
- package/crates/team-agent/src/provider/session/mod.rs +18 -0
- package/crates/team-agent/src/provider/session/resume.rs +57 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
- package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
- package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
- package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
- package/crates/team-agent/src/provider/session_scan.rs +8 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +23 -1
- package/crates/team-agent/src/state/persist.rs +301 -8
- package/crates/team-agent/src/state/repository.rs +5 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
- package/crates/team-agent/src/tmux_backend.rs +1064 -75
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +248 -1
- package/npm/install.mjs +129 -73
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +33 -238
- package/skills/team-agent/command-coverage.json +31 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
package/Cargo.lock
CHANGED
|
@@ -325,6 +325,12 @@ version = "0.4.29"
|
|
|
325
325
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
326
326
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
327
327
|
|
|
328
|
+
[[package]]
|
|
329
|
+
name = "md5"
|
|
330
|
+
version = "0.7.0"
|
|
331
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
332
|
+
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
|
333
|
+
|
|
328
334
|
[[package]]
|
|
329
335
|
name = "memchr"
|
|
330
336
|
version = "2.8.0"
|
|
@@ -575,12 +581,13 @@ dependencies = [
|
|
|
575
581
|
|
|
576
582
|
[[package]]
|
|
577
583
|
name = "team-agent"
|
|
578
|
-
version = "0.5.
|
|
584
|
+
version = "0.5.68"
|
|
579
585
|
dependencies = [
|
|
580
586
|
"anyhow",
|
|
581
587
|
"chrono",
|
|
582
588
|
"conpty-transport",
|
|
583
589
|
"libc",
|
|
590
|
+
"md5",
|
|
584
591
|
"regex",
|
|
585
592
|
"rusqlite",
|
|
586
593
|
"serde",
|
package/Cargo.toml
CHANGED
|
@@ -1536,6 +1536,11 @@ pub fn cmd_doctor(args: &DoctorArgs) -> Result<CmdResult, CliError> {
|
|
|
1536
1536
|
args.team.as_deref(),
|
|
1537
1537
|
&mut value,
|
|
1538
1538
|
);
|
|
1539
|
+
crate::cli::diagnose::append_registry_channel_unbound_to_report(
|
|
1540
|
+
&args.workspace,
|
|
1541
|
+
args.team.as_deref(),
|
|
1542
|
+
&mut value,
|
|
1543
|
+
);
|
|
1539
1544
|
value
|
|
1540
1545
|
};
|
|
1541
1546
|
Ok(CmdResult::from_json(value, args.json))
|
|
@@ -208,12 +208,45 @@ pub(crate) fn diagnose_runtime_for_workspace(
|
|
|
208
208
|
&mut issues,
|
|
209
209
|
&mut repairs,
|
|
210
210
|
);
|
|
211
|
+
append_registry_channel_unbound_issue(workspace, state, &mut issues, &mut repairs);
|
|
211
212
|
append_legacy_snapshot_issue(workspace, state, &mut issues);
|
|
212
213
|
append_coordinator_health_issue(workspace, state, &mut issues, &mut repairs);
|
|
213
214
|
append_runtime_bindings_stale_after_boot_issue(workspace, state, &mut issues, &mut repairs);
|
|
214
215
|
(issues, repairs)
|
|
215
216
|
}
|
|
216
217
|
|
|
218
|
+
fn append_registry_channel_unbound_issue(
|
|
219
|
+
workspace: &std::path::Path,
|
|
220
|
+
state: &Value,
|
|
221
|
+
issues: &mut Value,
|
|
222
|
+
repairs: &mut Value,
|
|
223
|
+
) {
|
|
224
|
+
let team_key = crate::state::projection::team_state_key(state);
|
|
225
|
+
if team_key.is_empty()
|
|
226
|
+
|| crate::lifecycle::launch::launched_team_receiver_is_attached(workspace, &team_key)
|
|
227
|
+
{
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if let Some(items) = issues.as_array_mut() {
|
|
231
|
+
if !items.iter().any(|item| {
|
|
232
|
+
item.as_str() == Some("leader_not_attached")
|
|
233
|
+
|| item.get("id").and_then(Value::as_str) == Some("leader_channel_unbound")
|
|
234
|
+
}) {
|
|
235
|
+
items.push(json!("leader_not_attached"));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if let Some(items) = repairs.as_array_mut() {
|
|
239
|
+
items.push(recovery_hint(
|
|
240
|
+
state
|
|
241
|
+
.get("session_name")
|
|
242
|
+
.and_then(Value::as_str)
|
|
243
|
+
.unwrap_or(team_key.as_str()),
|
|
244
|
+
"leader_not_attached",
|
|
245
|
+
"team-agent claim-leader",
|
|
246
|
+
));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
217
250
|
fn append_live_leader_workspace_mismatch_issue(
|
|
218
251
|
workspace: &std::path::Path,
|
|
219
252
|
state: &Value,
|
|
@@ -339,6 +372,53 @@ fn live_leader_workspace_mismatch(
|
|
|
339
372
|
Some((issue, repair))
|
|
340
373
|
}
|
|
341
374
|
|
|
375
|
+
pub(crate) fn append_registry_channel_unbound_to_report(
|
|
376
|
+
workspace: &std::path::Path,
|
|
377
|
+
team: Option<&str>,
|
|
378
|
+
report: &mut Value,
|
|
379
|
+
) {
|
|
380
|
+
let team_key = team
|
|
381
|
+
.filter(|team| !team.is_empty())
|
|
382
|
+
.map(str::to_string)
|
|
383
|
+
.or_else(|| {
|
|
384
|
+
crate::state::persist::load_runtime_state(workspace)
|
|
385
|
+
.ok()
|
|
386
|
+
.map(|state| crate::state::projection::team_state_key(&state))
|
|
387
|
+
})
|
|
388
|
+
.unwrap_or_default();
|
|
389
|
+
if team_key.is_empty()
|
|
390
|
+
|| crate::lifecycle::launch::launched_team_receiver_is_attached(workspace, &team_key)
|
|
391
|
+
{
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
let Some(object) = report.as_object_mut() else {
|
|
395
|
+
return;
|
|
396
|
+
};
|
|
397
|
+
let mut issues = object
|
|
398
|
+
.get("issues")
|
|
399
|
+
.and_then(Value::as_array)
|
|
400
|
+
.cloned()
|
|
401
|
+
.unwrap_or_default();
|
|
402
|
+
if !issues.iter().any(|item| {
|
|
403
|
+
item.as_str() == Some("leader_not_attached")
|
|
404
|
+
|| item.get("id").and_then(Value::as_str) == Some("leader_channel_unbound")
|
|
405
|
+
}) {
|
|
406
|
+
issues.push(json!("leader_not_attached"));
|
|
407
|
+
}
|
|
408
|
+
object.insert("issues".to_string(), Value::Array(issues));
|
|
409
|
+
let mut repairs = object
|
|
410
|
+
.get("suggested_repairs")
|
|
411
|
+
.and_then(Value::as_array)
|
|
412
|
+
.cloned()
|
|
413
|
+
.unwrap_or_default();
|
|
414
|
+
repairs.push(recovery_hint(
|
|
415
|
+
&team_key,
|
|
416
|
+
"leader_not_attached",
|
|
417
|
+
"team-agent claim-leader",
|
|
418
|
+
));
|
|
419
|
+
object.insert("suggested_repairs".to_string(), Value::Array(repairs));
|
|
420
|
+
}
|
|
421
|
+
|
|
342
422
|
pub(crate) fn append_selected_live_leader_workspace_mismatch(
|
|
343
423
|
workspace: &std::path::Path,
|
|
344
424
|
team: Option<&str>,
|
|
@@ -1485,6 +1565,8 @@ pub(crate) fn provider_doctor_checks() -> Value {
|
|
|
1485
1565
|
crate::provider::Provider::ClaudeCode,
|
|
1486
1566
|
crate::provider::Provider::Codex,
|
|
1487
1567
|
crate::provider::Provider::GeminiCli,
|
|
1568
|
+
crate::provider::Provider::Grok,
|
|
1569
|
+
crate::provider::Provider::CursorAgent,
|
|
1488
1570
|
crate::provider::Provider::Fake,
|
|
1489
1571
|
] {
|
|
1490
1572
|
let adapter = crate::provider::get_adapter(provider);
|
|
@@ -45,7 +45,7 @@ fn emit_with_json_order(
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
///
|
|
48
|
-
/// `main(argv)`(`parser.py:84`):**CLI 唯一进程入口**。codex/claude/copilot passthrough 早返回 →
|
|
48
|
+
/// `main(argv)`(`parser.py:84`):**CLI 唯一进程入口**。codex/claude/copilot/grok/cursor passthrough 早返回 →
|
|
49
49
|
/// 解析 argv 到 subcommand → 调对应 handler → 异常落盘 + 信封 + `ExitCode::Error` →
|
|
50
50
|
/// `consume_leader_inbox_summary` → `emit` → `result.ok is False ? Error : Ok`。
|
|
51
51
|
/// **行为入口**:契约可端到端跑 argv→(stdout, exit code)。
|
|
@@ -262,7 +262,7 @@ const DISPATCH_COMMANDS: &[&str] = &[
|
|
|
262
262
|
const SPEC_ONLY_HELP_COMMANDS: &[&str] = &["start"];
|
|
263
263
|
// Command grammar, not provider identity parsing: these are top-level CLI
|
|
264
264
|
// passthrough verbs for starting a leader under a provider executable.
|
|
265
|
-
const LEADER_PASSTHROUGH_COMMANDS: &[&str] = &["codex", "claude", "copilot"];
|
|
265
|
+
const LEADER_PASSTHROUGH_COMMANDS: &[&str] = &["codex", "claude", "copilot", "grok", "cursor"];
|
|
266
266
|
|
|
267
267
|
fn is_leader_passthrough_command(command: &str) -> bool {
|
|
268
268
|
LEADER_PASSTHROUGH_COMMANDS.contains(&command)
|
|
@@ -310,7 +310,7 @@ fn default_help() -> String {
|
|
|
310
310
|
"Guided recovery",
|
|
311
311
|
&["claim-leader", "takeover", "attach-leader"],
|
|
312
312
|
);
|
|
313
|
-
out.push_str("\nProvider launchers:\n team-agent codex|claude|copilot ...\n");
|
|
313
|
+
out.push_str("\nProvider launchers:\n team-agent codex|claude|copilot|grok|cursor ...\n");
|
|
314
314
|
out.push_str("\nRun `team-agent <command> --help` for command flags.");
|
|
315
315
|
out
|
|
316
316
|
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 对账 .grok/config.toml 残余共享槽与在役 grok 席,含未首 turn 危险窗
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: grok_slot_report
|
|
6
|
+
//! what: 盘上 ID/OWNER/AUTH、在役 grok、pre_first_turn;读不出为 unjudgeable
|
|
7
|
+
//! boundary:
|
|
8
|
+
//! - 读不出不报一致
|
|
9
|
+
//! - 不写盘、不改 overlay
|
|
10
|
+
//! maturity: wired
|
|
11
|
+
//! ---
|
|
12
|
+
|
|
13
|
+
use serde_json::{json, Value};
|
|
14
|
+
use std::path::Path;
|
|
15
|
+
|
|
16
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
17
|
+
pub(crate) struct GrokSlotReport {
|
|
18
|
+
pub readable: bool,
|
|
19
|
+
pub consistent: bool,
|
|
20
|
+
pub disk_team_agent_id: Option<String>,
|
|
21
|
+
pub disk_owner_team_id: Option<String>,
|
|
22
|
+
pub disk_auth_mode: Option<String>,
|
|
23
|
+
pub expected_owner_team_id: Option<String>,
|
|
24
|
+
pub expected_auth_mode: Option<String>,
|
|
25
|
+
pub live_seats: Vec<String>,
|
|
26
|
+
pub pre_first_turn: Vec<String>,
|
|
27
|
+
pub reason: String,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl GrokSlotReport {
|
|
31
|
+
pub(crate) fn to_json(&self) -> Value {
|
|
32
|
+
json!({
|
|
33
|
+
"readable": self.readable,
|
|
34
|
+
"consistent": self.consistent,
|
|
35
|
+
"disk_team_agent_id": self.disk_team_agent_id,
|
|
36
|
+
"disk_owner_team_id": self.disk_owner_team_id,
|
|
37
|
+
"disk_auth_mode": self.disk_auth_mode,
|
|
38
|
+
"expected_owner_team_id": self.expected_owner_team_id,
|
|
39
|
+
"expected_auth_mode": self.expected_auth_mode,
|
|
40
|
+
"live_seats": self.live_seats,
|
|
41
|
+
"pre_first_turn": self.pre_first_turn,
|
|
42
|
+
"reason": self.reason,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub(crate) fn grok_slot_report(workspace: &Path, state: &Value) -> GrokSlotReport {
|
|
48
|
+
if let Some(reason) = unreadable_state_reason(workspace, state) {
|
|
49
|
+
return unjudgeable(reason, Vec::new(), Vec::new());
|
|
50
|
+
}
|
|
51
|
+
let live = live_grok_from_state(state);
|
|
52
|
+
let pre_first_turn = live
|
|
53
|
+
.iter()
|
|
54
|
+
.filter(|seat| seat.pre_first_turn)
|
|
55
|
+
.map(|seat| seat.id.clone())
|
|
56
|
+
.collect::<Vec<_>>();
|
|
57
|
+
let live_ids = live.iter().map(|seat| seat.id.clone()).collect::<Vec<_>>();
|
|
58
|
+
let expected_owner = expected_owner_team_id(state);
|
|
59
|
+
let expected_auth = expected_auth_mode(&live);
|
|
60
|
+
|
|
61
|
+
let toml_path = workspace.join(".grok").join("config.toml");
|
|
62
|
+
if toml_path.exists() {
|
|
63
|
+
match std::fs::read_to_string(&toml_path) {
|
|
64
|
+
Err(error) => {
|
|
65
|
+
return unjudgeable(
|
|
66
|
+
format!("unjudgeable: cannot read {} ({error})", toml_path.display()),
|
|
67
|
+
live_ids,
|
|
68
|
+
pre_first_turn,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
Ok(text) => {
|
|
72
|
+
let disk = parse_disk_slot(&text);
|
|
73
|
+
return classify(disk, live_ids, pre_first_turn, expected_owner, expected_auth);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
classify(
|
|
78
|
+
DiskSlot::default(),
|
|
79
|
+
live_ids,
|
|
80
|
+
pre_first_turn,
|
|
81
|
+
expected_owner,
|
|
82
|
+
expected_auth,
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
87
|
+
struct DiskSlot {
|
|
88
|
+
team_agent_id: Option<String>,
|
|
89
|
+
owner_team_id: Option<String>,
|
|
90
|
+
auth_mode: Option<String>,
|
|
91
|
+
extras: Vec<(String, String)>,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn parse_disk_slot(text: &str) -> DiskSlot {
|
|
95
|
+
let keys = crate::lifecycle::launch::per_seat_keys_in_toml(text);
|
|
96
|
+
DiskSlot {
|
|
97
|
+
team_agent_id: find_key(&keys, "TEAM_AGENT_ID"),
|
|
98
|
+
owner_team_id: find_key(&keys, "TEAM_AGENT_OWNER_TEAM_ID"),
|
|
99
|
+
auth_mode: find_key(&keys, "TEAM_AGENT_AUTH_MODE"),
|
|
100
|
+
extras: keys
|
|
101
|
+
.into_iter()
|
|
102
|
+
.filter(|(key, _)| {
|
|
103
|
+
key != "TEAM_AGENT_ID"
|
|
104
|
+
&& key != "TEAM_AGENT_OWNER_TEAM_ID"
|
|
105
|
+
&& key != "TEAM_AGENT_AUTH_MODE"
|
|
106
|
+
})
|
|
107
|
+
.collect(),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn find_key(keys: &[(String, String)], want: &str) -> Option<String> {
|
|
112
|
+
keys.iter()
|
|
113
|
+
.find(|(key, _)| key == want)
|
|
114
|
+
.map(|(_, value)| value.clone())
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn unjudgeable(
|
|
118
|
+
reason: String,
|
|
119
|
+
live_seats: Vec<String>,
|
|
120
|
+
pre_first_turn: Vec<String>,
|
|
121
|
+
) -> GrokSlotReport {
|
|
122
|
+
GrokSlotReport {
|
|
123
|
+
readable: false,
|
|
124
|
+
consistent: false,
|
|
125
|
+
disk_team_agent_id: None,
|
|
126
|
+
disk_owner_team_id: None,
|
|
127
|
+
disk_auth_mode: None,
|
|
128
|
+
expected_owner_team_id: None,
|
|
129
|
+
expected_auth_mode: None,
|
|
130
|
+
live_seats,
|
|
131
|
+
pre_first_turn,
|
|
132
|
+
reason,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fn unreadable_state_reason(workspace: &Path, state: &Value) -> Option<String> {
|
|
137
|
+
let path = workspace.join(".team").join("runtime").join("state.json");
|
|
138
|
+
if !path.exists() {
|
|
139
|
+
return None;
|
|
140
|
+
}
|
|
141
|
+
match std::fs::read_to_string(&path) {
|
|
142
|
+
Err(error) => Some(format!(
|
|
143
|
+
"unjudgeable: cannot read {} ({error})",
|
|
144
|
+
path.display()
|
|
145
|
+
)),
|
|
146
|
+
Ok(text) => match serde_json::from_str::<Value>(&text) {
|
|
147
|
+
Err(error) => Some(format!(
|
|
148
|
+
"unjudgeable: cannot parse {} ({error})",
|
|
149
|
+
path.display()
|
|
150
|
+
)),
|
|
151
|
+
Ok(_) if state.is_null() => {
|
|
152
|
+
Some("unjudgeable: runtime state exists but could not be loaded".to_string())
|
|
153
|
+
}
|
|
154
|
+
Ok(_) => None,
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fn classify(
|
|
160
|
+
disk: DiskSlot,
|
|
161
|
+
live_ids: Vec<String>,
|
|
162
|
+
pre_first_turn: Vec<String>,
|
|
163
|
+
expected_owner: Option<String>,
|
|
164
|
+
expected_auth: Option<String>,
|
|
165
|
+
) -> GrokSlotReport {
|
|
166
|
+
let mut mismatches = Vec::new();
|
|
167
|
+
if let Some(disk_id) = disk.team_agent_id.as_deref() {
|
|
168
|
+
mismatches.push(format!("per-seat key TEAM_AGENT_ID={disk_id}"));
|
|
169
|
+
}
|
|
170
|
+
if let Some(disk_owner) = disk.owner_team_id.as_deref() {
|
|
171
|
+
mismatches.push(format!("per-seat key TEAM_AGENT_OWNER_TEAM_ID={disk_owner}"));
|
|
172
|
+
}
|
|
173
|
+
if let Some(disk_auth) = disk.auth_mode.as_deref() {
|
|
174
|
+
mismatches.push(format!("per-seat key TEAM_AGENT_AUTH_MODE={disk_auth}"));
|
|
175
|
+
}
|
|
176
|
+
for (key, value) in &disk.extras {
|
|
177
|
+
mismatches.push(format!("per-seat key {key}={value}"));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if mismatches.is_empty() {
|
|
181
|
+
let reason =
|
|
182
|
+
"toml carries no per-seat identity keys; identity inherits from pane env".to_string();
|
|
183
|
+
return GrokSlotReport {
|
|
184
|
+
readable: true,
|
|
185
|
+
consistent: true,
|
|
186
|
+
disk_team_agent_id: disk.team_agent_id,
|
|
187
|
+
disk_owner_team_id: disk.owner_team_id,
|
|
188
|
+
disk_auth_mode: disk.auth_mode,
|
|
189
|
+
expected_owner_team_id: expected_owner,
|
|
190
|
+
expected_auth_mode: expected_auth,
|
|
191
|
+
live_seats: live_ids,
|
|
192
|
+
pre_first_turn,
|
|
193
|
+
reason,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
GrokSlotReport {
|
|
198
|
+
readable: true,
|
|
199
|
+
consistent: false,
|
|
200
|
+
disk_team_agent_id: disk.team_agent_id,
|
|
201
|
+
disk_owner_team_id: disk.owner_team_id,
|
|
202
|
+
disk_auth_mode: disk.auth_mode,
|
|
203
|
+
expected_owner_team_id: expected_owner,
|
|
204
|
+
expected_auth_mode: expected_auth,
|
|
205
|
+
live_seats: live_ids,
|
|
206
|
+
pre_first_turn,
|
|
207
|
+
reason: format!("grok slot mismatch: {}", mismatches.join("; ")),
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
struct LiveGrok {
|
|
212
|
+
id: String,
|
|
213
|
+
pre_first_turn: bool,
|
|
214
|
+
auth_mode: Option<String>,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fn live_grok_from_state(state: &Value) -> Vec<LiveGrok> {
|
|
218
|
+
let mut out = Vec::new();
|
|
219
|
+
push_live_from_map(state.get("agents").and_then(Value::as_object), &mut out);
|
|
220
|
+
if let Some(teams) = state.get("teams").and_then(Value::as_object) {
|
|
221
|
+
for team in teams.values() {
|
|
222
|
+
push_live_from_map(team.get("agents").and_then(Value::as_object), &mut out);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
out
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn push_live_from_map(agents: Option<&serde_json::Map<String, Value>>, out: &mut Vec<LiveGrok>) {
|
|
229
|
+
let Some(agents) = agents else {
|
|
230
|
+
return;
|
|
231
|
+
};
|
|
232
|
+
for (id, agent) in agents {
|
|
233
|
+
if out.iter().any(|row| row.id == *id) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
let provider = agent.get("provider").and_then(Value::as_str).unwrap_or("");
|
|
237
|
+
if provider != "grok" {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
let status = agent
|
|
241
|
+
.get("status")
|
|
242
|
+
.and_then(Value::as_str)
|
|
243
|
+
.unwrap_or("running");
|
|
244
|
+
if matches!(
|
|
245
|
+
status,
|
|
246
|
+
"stopped" | "stopping" | "removed" | "spawn_failed" | "failed"
|
|
247
|
+
) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
let first = agent
|
|
251
|
+
.get("first_send_at")
|
|
252
|
+
.and_then(Value::as_str)
|
|
253
|
+
.filter(|s| !s.is_empty());
|
|
254
|
+
out.push(LiveGrok {
|
|
255
|
+
id: id.clone(),
|
|
256
|
+
pre_first_turn: first.is_none(),
|
|
257
|
+
auth_mode: agent_auth_mode(agent),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fn agent_auth_mode(agent: &Value) -> Option<String> {
|
|
263
|
+
match agent.get("auth_mode") {
|
|
264
|
+
Some(Value::String(s)) if !s.trim().is_empty() => Some(s.clone()),
|
|
265
|
+
Some(other) if !other.is_null() => Some(other.to_string().trim_matches('"').to_string()),
|
|
266
|
+
_ => None,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
fn expected_owner_team_id(state: &Value) -> Option<String> {
|
|
271
|
+
for key in ["active_team_key", "team_key", "TEAM_AGENT_OWNER_TEAM_ID"] {
|
|
272
|
+
if let Some(value) = state
|
|
273
|
+
.get(key)
|
|
274
|
+
.and_then(Value::as_str)
|
|
275
|
+
.map(str::trim)
|
|
276
|
+
.filter(|s| !s.is_empty())
|
|
277
|
+
{
|
|
278
|
+
return Some(value.to_string());
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
None
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
fn expected_auth_mode(live: &[LiveGrok]) -> Option<String> {
|
|
285
|
+
let mut found = None;
|
|
286
|
+
for seat in live {
|
|
287
|
+
let Some(auth) = seat.auth_mode.as_deref() else {
|
|
288
|
+
continue;
|
|
289
|
+
};
|
|
290
|
+
match found {
|
|
291
|
+
None => found = Some(auth.to_string()),
|
|
292
|
+
Some(ref existing) if existing != auth => return None,
|
|
293
|
+
Some(_) => {}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
found
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//!
|
|
2
|
-
//! cli · leader — `codex`/`claude`/`copilot` passthrough(`cmd_leader_passthrough` + `_provider_args` /
|
|
2
|
+
//! cli · leader — `codex`/`claude`/`copilot`/`grok`/`cursor` passthrough(`cmd_leader_passthrough` + `_provider_args` /
|
|
3
3
|
//! `_leader_launcher_args`)+ leader fallback inbox 摘要(`consume_leader_inbox_summary` 及
|
|
4
4
|
//! `_leader_inbox_entries` / `_leader_inbox_summary` / `_leader_inbox_entry_title`)。
|
|
5
5
|
|
|
@@ -96,9 +96,9 @@ fn without_leader_json(values: &[String]) -> Vec<String> {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
///
|
|
99
|
-
/// `codex`/`claude`/`copilot` passthrough(`parser.py:86`/`_run_leader_passthrough`):leader 早返回,
|
|
99
|
+
/// `codex`/`claude`/`copilot`/`grok`/`cursor` passthrough(`parser.py:86`/`_run_leader_passthrough`):leader 早返回,
|
|
100
100
|
/// **不**进 subparser。`-h`/`--help` 打 usage 直接返回 [`CmdResult::none`]。否则解析 attach
|
|
101
|
-
/// 旗标 + `lifecycle_port::start_leader`。`command`
|
|
101
|
+
/// 旗标 + `lifecycle_port::start_leader`。`command` 必须是闭集动词,禁止 command-text 回落到 ClaudeCode。
|
|
102
102
|
pub fn cmd_leader_passthrough(
|
|
103
103
|
command: &str,
|
|
104
104
|
provider_args: &[String],
|
|
@@ -110,14 +110,28 @@ pub fn cmd_leader_passthrough(
|
|
|
110
110
|
let as_json = leader_launcher_json(provider_args);
|
|
111
111
|
let launcher_args = without_leader_json(provider_args);
|
|
112
112
|
let attach = leader_launcher_args(&launcher_args)?;
|
|
113
|
-
let provider = leader_passthrough_provider(command)
|
|
113
|
+
let provider = leader_passthrough_provider(command).ok_or_else(|| {
|
|
114
|
+
CliError::Usage(format!(
|
|
115
|
+
"unknown leader passthrough command: {command}"
|
|
116
|
+
))
|
|
117
|
+
})?;
|
|
114
118
|
let value = lifecycle_port::start_leader(provider, &attach.provider_args, cwd, &attach)?;
|
|
115
119
|
Ok(CmdResult::from_json(value, as_json))
|
|
116
120
|
}
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
/// Closed-set verb → Provider. `None` = not a passthrough verb (must not become ClaudeCode).
|
|
123
|
+
pub(crate) fn leader_passthrough_provider(
|
|
124
|
+
command: &str,
|
|
125
|
+
) -> Option<crate::model::enums::Provider> {
|
|
126
|
+
use crate::model::enums::Provider;
|
|
127
|
+
match command {
|
|
128
|
+
"codex" => Some(Provider::Codex),
|
|
129
|
+
"claude" => Some(Provider::ClaudeCode),
|
|
130
|
+
"copilot" => Some(Provider::Copilot),
|
|
131
|
+
"grok" => Some(Provider::Grok),
|
|
132
|
+
"cursor" => Some(Provider::CursorAgent),
|
|
133
|
+
_ => None,
|
|
134
|
+
}
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
// =============================================================================
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: `team-agent leaders` 发现面。人读默认面按节点列出 workspace 全路径、
|
|
3
|
+
//! team/leader、可复制 `team-agent send '<ws>::<team>/leader' '<消息>'`。
|
|
4
|
+
//! contract:
|
|
5
|
+
//! provides:
|
|
6
|
+
//! - name: cmd_leaders
|
|
7
|
+
//! what: --json 保持 E7 字段(含 hash/send_hint); 默认 stdout 只有三要素、不含 hash/channel/pane id
|
|
8
|
+
//! boundary:
|
|
9
|
+
//! - 不改 send 解析; 不把 --json 字段从默认面漏出去
|
|
10
|
+
//! - Leaders-only; worker 行仍缺席
|
|
11
|
+
//! ---
|
|
1
12
|
//!
|
|
2
13
|
//! E7 (0.5.9 host-leader-registry-design §4.1): `team-agent leaders` command.
|
|
3
14
|
//!
|
|
@@ -92,5 +103,118 @@ pub fn cmd_leaders(args: &LeadersArgs) -> Result<CmdResult, CliError> {
|
|
|
92
103
|
// reason for STALE/AMBIGUOUS entries.
|
|
93
104
|
"status_wire_values": ["LIVE", "STALE", "AMBIGUOUS"],
|
|
94
105
|
});
|
|
95
|
-
|
|
106
|
+
if args.json {
|
|
107
|
+
return Ok(CmdResult::from_json(value, true));
|
|
108
|
+
}
|
|
109
|
+
let rows: Vec<(String, String)> = classified
|
|
110
|
+
.iter()
|
|
111
|
+
.map(|(entry, _, _)| {
|
|
112
|
+
(
|
|
113
|
+
entry.workspace.display().to_string(),
|
|
114
|
+
entry.team_key.clone(),
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
.collect();
|
|
118
|
+
Ok(CmdResult::human(format_leaders_human(&rows)))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn quote_cli_arg(raw: &str) -> String {
|
|
122
|
+
if !raw.contains('\'') {
|
|
123
|
+
format!("'{raw}'")
|
|
124
|
+
} else {
|
|
125
|
+
format!("\"{}\"", raw.replace('\\', "\\\\").replace('"', "\\\""))
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Default human face: workspace path, `<team>/leader`, copyable send line.
|
|
130
|
+
fn format_leaders_human(rows: &[(String, String)]) -> String {
|
|
131
|
+
if rows.is_empty() {
|
|
132
|
+
return "no registered leaders\n".to_string();
|
|
133
|
+
}
|
|
134
|
+
let mut out = String::new();
|
|
135
|
+
for (workspace, team_key) in rows {
|
|
136
|
+
let role = format!("{team_key}/leader");
|
|
137
|
+
let to = format!("{workspace}::{role}");
|
|
138
|
+
out.push_str(workspace);
|
|
139
|
+
out.push('\n');
|
|
140
|
+
out.push_str(&role);
|
|
141
|
+
out.push('\n');
|
|
142
|
+
out.push_str("team-agent send ");
|
|
143
|
+
out.push_str("e_cli_arg(&to));
|
|
144
|
+
out.push(' ');
|
|
145
|
+
out.push_str("e_cli_arg("消息"));
|
|
146
|
+
out.push('\n');
|
|
147
|
+
}
|
|
148
|
+
out
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[cfg(test)]
|
|
152
|
+
mod tests {
|
|
153
|
+
use super::*;
|
|
154
|
+
|
|
155
|
+
fn sample_human() -> String {
|
|
156
|
+
format_leaders_human(&[
|
|
157
|
+
(
|
|
158
|
+
"/Volumes/nvme/Projects/讨论team-agent".to_string(),
|
|
159
|
+
"wiki-team".to_string(),
|
|
160
|
+
),
|
|
161
|
+
(
|
|
162
|
+
"/Users/alauda/Documents/code/agent前沿探索/多agent协作".to_string(),
|
|
163
|
+
"refactor-maintainability".to_string(),
|
|
164
|
+
),
|
|
165
|
+
])
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[test]
|
|
169
|
+
fn leaders_human_lists_workspace_team_role_and_copyable_send() {
|
|
170
|
+
let out = sample_human();
|
|
171
|
+
assert!(
|
|
172
|
+
out.contains("team-agent send '/"),
|
|
173
|
+
"A: copyable send TO must start with absolute workspace path; got {out:?}"
|
|
174
|
+
);
|
|
175
|
+
assert!(
|
|
176
|
+
out.contains("::wiki-team/leader'"),
|
|
177
|
+
"B: send TO must contain ::<team>/<role>; got {out:?}"
|
|
178
|
+
);
|
|
179
|
+
assert!(
|
|
180
|
+
out.contains(
|
|
181
|
+
"team-agent send '/Volumes/nvme/Projects/讨论team-agent::wiki-team/leader' '消息'"
|
|
182
|
+
),
|
|
183
|
+
"C: complete copyable send line; got {out:?}"
|
|
184
|
+
);
|
|
185
|
+
assert!(out.contains("/Volumes/nvme/Projects/讨论team-agent\nwiki-team/leader\n"));
|
|
186
|
+
assert!(out.contains(
|
|
187
|
+
"/Users/alauda/Documents/code/agent前沿探索/多agent协作\nrefactor-maintainability/leader\n"
|
|
188
|
+
));
|
|
189
|
+
assert!(out.contains(
|
|
190
|
+
"team-agent send '/Users/alauda/Documents/code/agent前沿探索/多agent协作::refactor-maintainability/leader' '消息'"
|
|
191
|
+
));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#[test]
|
|
195
|
+
fn leaders_human_omits_hash_channel_and_pane_id_fields() {
|
|
196
|
+
let out = sample_human();
|
|
197
|
+
for noise in [
|
|
198
|
+
"workspace_hash",
|
|
199
|
+
"stable_qualified_name",
|
|
200
|
+
"channel_id",
|
|
201
|
+
"\"channel\":",
|
|
202
|
+
"pane_id",
|
|
203
|
+
] {
|
|
204
|
+
assert!(
|
|
205
|
+
!out.contains(noise),
|
|
206
|
+
"D: default surface must not print {noise}; got {out:?}"
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
let empty = format_leaders_human(&[]);
|
|
210
|
+
for noise in [
|
|
211
|
+
"workspace_hash",
|
|
212
|
+
"stable_qualified_name",
|
|
213
|
+
"channel_id",
|
|
214
|
+
"\"channel\":",
|
|
215
|
+
"pane_id",
|
|
216
|
+
] {
|
|
217
|
+
assert!(!empty.contains(noise), "empty surface leaked {noise}");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
96
220
|
}
|