返回 CodeWhale
relay.rs
根目录 / crates / tui / src / commands / groups / session / relay.rs
1 //! `/relay` command.
2
3 use std::fmt::Write as _;
4
5 use crate::commands::traits::{CommandInfo, RegisterCommand};
6 use crate::localization::MessageId;
7 use crate::tui::app::{App, AppAction};
8
9 use super::CommandResult;
10
11 pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
12 name: "relay",
13 aliases: &["batonpass", "接力"],
14 usage: "/relay [focus]",
15 description_id: MessageId::CmdRelayDescription,
16 };
17
18 pub(in crate::commands) struct RelayCmd;
19
20 impl RegisterCommand for RelayCmd {
21 fn info() -> &'static CommandInfo {
22 &COMMAND_INFO
23 }
24
25 fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {
26 relay(app, arg)
27 }
28 }
29
30 /// Ask the active model to write a compact relay artifact for the next thread.
31 ///
32 /// The visible command is `/relay` (with `/接力` for Chinese users), but the
33 /// durable file path remains `.deepseek/handoff.md` for compatibility with
34 /// existing sessions and startup prompt loading.
35 pub fn relay(app: &mut App, arg: Option<&str>) -> CommandResult {
36 let focus = arg.map(str::trim).filter(|value| !value.is_empty());
37 let message = build_relay_instruction(app, focus);
38 CommandResult::with_message_and_action(
39 "Preparing session relay at .deepseek/handoff.md...",
40 AppAction::SendMessage(message),
41 )
42 }
43
44 fn build_relay_instruction(app: &App, focus: Option<&str>) -> String {
45 let mut out = String::new();
46 let _ = writeln!(
47 out,
48 "Create a compact session relay (接力) for a future Codewhale thread."
49 );
50 let _ = writeln!(out);
51 let _ = writeln!(out, "Write or update `.deepseek/handoff.md`.");
52 let _ = writeln!(
53 out,
54 "Keep the existing file path for compatibility, but title the artifact `# Session relay`."
55 );
56 let _ = writeln!(out);
57 let _ = writeln!(out, "Use this relay structure:");
58 let _ = writeln!(out);
59 let _ = writeln!(out, "{}", crate::prompts::COMPACT_TEMPLATE.trim());
60 let _ = writeln!(out);
61 let _ = writeln!(out, "Current session snapshot:");
62 let _ = writeln!(out, "- Workspace: {}", app.workspace.display());
63 let _ = writeln!(out, "- Mode: {}", app.mode.label());
64 let _ = writeln!(out, "- Model: {}", app.model_display_label());
65 if let Some(focus) = focus {
66 let _ = writeln!(out, "- Requested relay focus: {focus}");
67 }
68 if let Some(quarry) = app.hunt.quarry.as_deref() {
69 let _ = writeln!(out, "- Goal objective: {quarry}");
70 }
71 if let Some(budget) = app.hunt.token_budget {
72 let _ = writeln!(out, "- Goal token budget: {budget}");
73 }
74 // Read through the same authoritative graph-backed snapshot seam used by
75 // persistence. A model can call `work_update` immediately before `/relay`;
76 // the UI projection may not have published yet, but the handoff must still
77 // describe the staged Work state rather than a stale compatibility list.
78 match app.work_state_snapshot() {
79 Ok(Some(state)) => {
80 if let Some(body) = crate::work_grounding::canonical_todo_body(&state.todos) {
81 let _ = writeln!(out, "\nCurrent Work state (the To-do ledger is canonical):");
82 let _ = writeln!(out, "{body}");
83 }
84 }
85 Ok(None) => {}
86 Err(_) => {
87 let _ = writeln!(out, "\nTo-do: unavailable because the list is busy.");
88 }
89 }
90
91 if let Ok(plan) = app.plan_state.try_lock() {
92 let snapshot = plan.snapshot();
93 if !snapshot.is_empty() {
94 let _ = writeln!(
95 out,
96 "\nConversational strategy notes from update_plan (reasoning context, not a Work surface):"
97 );
98 write_plan_field(&mut out, "Title", snapshot.title.as_deref());
99 write_plan_field(&mut out, "Objective", snapshot.objective.as_deref());
100 write_plan_field(&mut out, "Context", snapshot.context_summary.as_deref());
101 write_plan_field(&mut out, "Explanation", snapshot.explanation.as_deref());
102 write_plan_list(&mut out, "Source", &snapshot.sources_used);
103 write_plan_list(&mut out, "Critical file", &snapshot.critical_files);
104 write_plan_list(&mut out, "Constraint", &snapshot.constraints);
105 write_plan_field(
106 &mut out,
107 "Recommended approach",
108 snapshot.recommended_approach.as_deref(),
109 );
110 write_plan_field(
111 &mut out,
112 "Verification plan",
113 snapshot.verification_plan.as_deref(),
114 );
115 write_plan_field(
116 &mut out,
117 "Risks and unknowns",
118 snapshot.risks_and_unknowns.as_deref(),
119 );
120 write_plan_field(
121 &mut out,
122 "Handoff packet",
123 snapshot.handoff_packet.as_deref(),
124 );
125 for item in snapshot.items {
126 let _ = writeln!(out, "- [{}] {}", plan_status_label(&item.status), item.step);
127 }
128 }
129 } else {
130 let _ = writeln!(
131 out,
132 "\nStrategy metadata: unavailable because plan state is busy."
133 );
134 }
135
136 let _ = writeln!(
137 out,
138 "\nBefore writing, inspect the current transcript context and any live tool evidence you need. Do not invent test results, file changes, blockers, or decisions."
139 );
140 let _ = writeln!(
141 out,
142 "\nKeep it under about 900 words unless the session genuinely needs more. After writing, report the path and the single next action."
143 );
144 out
145 }
146
147 fn write_plan_field(out: &mut String, label: &str, value: Option<&str>) {
148 if let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) {
149 let _ = writeln!(out, "- {label}: {value}");
150 }
151 }
152
153 fn write_plan_list(out: &mut String, label: &str, values: &[String]) {
154 for value in values {
155 let value = value.trim();
156 if !value.is_empty() {
157 let _ = writeln!(out, "- {label}: {value}");
158 }
159 }
160 }
161
162 fn plan_status_label(status: &crate::tools::plan::StepStatus) -> &'static str {
163 match status {
164 crate::tools::plan::StepStatus::Pending => "pending",
165 crate::tools::plan::StepStatus::InProgress => "in_progress",
166 crate::tools::plan::StepStatus::Completed => "completed",
167 }
168 }
169
169 lines RUST