refactor: remove workflow machinery

This commit is contained in:
2026-07-16 05:45:24 +09:00
parent 83ad7506d7
commit d801b2698b
64 changed files with 141 additions and 4055 deletions
+2 -10
View File
@@ -596,7 +596,6 @@ impl App {
match kind {
CompletionKind::File => self.input.replace_with_file_ref(start, value),
CompletionKind::Knowledge => self.input.replace_with_knowledge_ref(start, value),
CompletionKind::Workflow => self.input.replace_with_workflow_invoke(start, value),
}
self.completion = None;
true
@@ -631,7 +630,6 @@ impl App {
match kind {
CompletionKind::File => self.input.replace_with_file_ref(start, value),
CompletionKind::Knowledge => self.input.replace_with_knowledge_ref(start, value),
CompletionKind::Workflow => self.input.replace_with_workflow_invoke(start, value),
}
self.completion = None;
true
@@ -2170,12 +2168,12 @@ impl App {
}
session_store::SystemItem::FileAttachment { body, .. }
| session_store::SystemItem::Knowledge { body, .. }
| session_store::SystemItem::Workflow { body, .. }
| session_store::SystemItem::TaskReminder { body, .. }
| session_store::SystemItem::Interrupt { body } => {
self.task_store.apply_system_message_text(&body);
self.blocks.push(Block::SystemMessage { text: body });
}
session_store::SystemItem::LegacyIgnored { .. } => {}
}
}
@@ -2981,7 +2979,7 @@ mod completion_flow_tests {
let _ = app.refresh_completion();
// Reply for a different kind shouldn't overwrite state.
app.handle_worker_event(Event::Completions {
kind: CompletionKind::Workflow,
kind: CompletionKind::Knowledge,
entries: vec![CompletionEntry {
value: "stale".into(),
is_dir: false,
@@ -3707,12 +3705,6 @@ mod completion_flow_tests {
Segment::KnowledgeRef {
slug: "design-note".into(),
},
Segment::Text {
content: " then ".into(),
},
Segment::WorkflowInvoke {
slug: "review".into(),
},
Segment::Paste {
id: 1,
chars: 13,
+1 -1
View File
@@ -4949,7 +4949,7 @@ fn orchestrator_queue_notification_message(
) -> String {
let title = ticket.title.replace(['\r', '\n'], " ");
format!(
"Workspace Dashboard Queue for Ticket `{}`, title `{}`: human authorized Orchestrator routing; this is not an unattended scheduler. Read the Ticket and inspect current Orchestrator workspace state. If unblocked, record routing and transition state queued -> inprogress before any worktree/SpawnWorker implementation side effects. After inprogress acceptance, use worktree-workflow for `.worktree/<task-name>` creation with tracked `.yoi` project records visible and `.yoi/memory` plus local/runtime/log/lock/secret-like `.yoi` paths excluded, then use multi-agent-workflow to run sibling coder/reviewer Workers (coder narrow child-worktree write scope, reviewer read-only by default). After reviewer approval and blocker resolution, integrate the implementation branch into the orchestration branch automatically, validate in the Orchestrator worktree, record the outcome, and clean up only child implementation worktrees/branches. Do not read, write, validate, merge, clean up, or run git operations in the root/original workspace. If blocked, record a concise reason and leave the Ticket queued or return it to planning with the missing-information reason.",
"Workspace Dashboard Queue for Ticket `{}`, title `{}`: human authorized Orchestrator routing; this is not an unattended scheduler. Read the Ticket and inspect current Orchestrator workspace state. If unblocked, record routing and transition state queued -> inprogress before any worktree/SpawnWorker implementation side effects. After inprogress acceptance, create the delegated implementation worktree with tracked `.yoi` project records visible and generated/local/runtime/log/lock/secret-like `.yoi` paths excluded, then run sibling coder/reviewer Workers through typed Ticket role launch surfaces. After reviewer approval and blocker resolution, integrate the implementation branch into the orchestration branch automatically, validate in the Orchestrator worktree, record the outcome, and clean up only child implementation worktrees/branches. Do not read, write, validate, merge, clean up, or run git operations in the root/original workspace. If blocked, record a concise reason and leave the Ticket queued or return it to planning with the missing-information reason.",
ticket.id,
title.trim()
)
+2 -3
View File
@@ -825,7 +825,7 @@ fn ticket_queue_notification_message_carries_routing_contract() {
assert!(message.contains("transition state queued -> inprogress"));
assert!(message.contains("before any worktree/SpawnWorker implementation side effects"));
assert!(message.contains("After inprogress acceptance"));
assert!(message.contains("worktree-workflow"));
assert!(message.contains("implementation worktree"));
assert!(message.contains("`.worktree/<task-name>`"));
assert!(message.contains("tracked `.yoi` project records visible"));
assert!(
@@ -833,7 +833,7 @@ fn ticket_queue_notification_message_carries_routing_contract() {
"`.yoi/memory` plus local/runtime/log/lock/secret-like `.yoi` paths excluded"
)
);
assert!(message.contains("multi-agent-workflow"));
assert!(message.contains("sibling coder/reviewer Workers"));
assert!(message.contains("sibling coder/reviewer Workers"));
assert!(message.contains("coder narrow child-worktree write scope"));
assert!(message.contains("reviewer read-only by default"));
@@ -2791,7 +2791,6 @@ fn dashboard_ticket_intake_finish_success_clears_composer_and_reports_pod() {
role: TicketRole::Intake,
worker_name: "intake-worker".to_string(),
profile: "builtin:default".to_string(),
workflow: "ticket-intake-workflow".to_string(),
launch_prompt_ref: None,
run_segments: vec![],
},
+8 -84
View File
@@ -58,25 +58,12 @@ impl KnowledgeRefAtom {
}
}
/// `/<slug>` chip — confirmed completion of a Workflow invocation.
#[derive(Debug, Clone)]
pub struct WorkflowInvokeAtom {
pub slug: String,
}
impl WorkflowInvokeAtom {
pub fn label(&self) -> String {
format!("/{}", self.slug)
}
}
#[derive(Debug, Clone)]
pub enum Atom {
Char(char),
Paste(PasteRef),
FileRef(FileRefAtom),
KnowledgeRef(KnowledgeRefAtom),
WorkflowInvoke(WorkflowInvokeAtom),
}
impl Atom {
@@ -88,7 +75,6 @@ impl Atom {
Atom::Paste(p) => Some((Style::default().fg(Color::Magenta), p.label())),
Atom::FileRef(r) => Some((Style::default().fg(Color::Cyan), r.label())),
Atom::KnowledgeRef(r) => Some((Style::default().fg(Color::Green), r.label())),
Atom::WorkflowInvoke(r) => Some((Style::default().fg(Color::Yellow), r.label())),
}
}
}
@@ -97,8 +83,7 @@ impl Atom {
enum AtomClass {
Word(WordKind),
Sep,
/// Indivisible chip — paste / file ref / knowledge ref / workflow
/// invocation. Word motion treats one chip as one unit; deletion
/// Indivisible chip — paste / file ref / knowledge ref. Word motion treats one chip as one unit; deletion
/// removes the whole atom.
Chip,
}
@@ -118,9 +103,7 @@ enum WordKind {
fn atom_class(atom: &Atom) -> AtomClass {
match atom {
Atom::Char(c) => char_class(*c),
Atom::Paste(_) | Atom::FileRef(_) | Atom::KnowledgeRef(_) | Atom::WorkflowInvoke(_) => {
AtomClass::Chip
}
Atom::Paste(_) | Atom::FileRef(_) | Atom::KnowledgeRef(_) => AtomClass::Chip,
}
}
@@ -216,11 +199,6 @@ impl InputBuffer {
self.atoms
.push(Atom::KnowledgeRef(KnowledgeRefAtom { slug: slug.clone() }));
}
protocol::Segment::WorkflowInvoke { slug } => {
self.atoms.push(Atom::WorkflowInvoke(WorkflowInvokeAtom {
slug: slug.clone(),
}));
}
protocol::Segment::Unknown => {
self.atoms
.extend("[unknown input segment]".chars().map(Atom::Char));
@@ -249,7 +227,6 @@ impl InputBuffer {
Atom::Paste(paste) => text.push_str(&paste.content),
Atom::FileRef(file) => text.push_str(&file.path),
Atom::KnowledgeRef(knowledge) => text.push_str(&knowledge.slug),
Atom::WorkflowInvoke(workflow) => text.push_str(&workflow.slug),
}
}
text
@@ -277,7 +254,7 @@ impl InputBuffer {
}
/// Replace `atoms[start..self.cursor]` (the in-flight `@<typed>` /
/// `#<typed>` / `/<typed>` token) with the corresponding chip atom
/// `#<typed>` token) with the corresponding chip atom
/// and place the cursor right after the chip. Used by the completion
/// confirm path.
pub fn replace_with_file_ref(&mut self, start: usize, path: String) {
@@ -294,13 +271,6 @@ impl InputBuffer {
self.cursor = start + 1;
}
pub fn replace_with_workflow_invoke(&mut self, start: usize, slug: String) {
self.atoms.drain(start..self.cursor);
self.atoms
.insert(start, Atom::WorkflowInvoke(WorkflowInvokeAtom { slug }));
self.cursor = start + 1;
}
/// Replace `atoms[start..self.cursor]` with the chars of `text`,
/// leaving cursor at the end of the inserted run. Used by the Tab
/// completion path: the popup-selected entry is inserted as raw
@@ -322,9 +292,9 @@ impl InputBuffer {
/// after the sigil (sigil itself excluded).
///
/// Trigger rules:
/// - The sigil (`@` / `#` / `/`) must be preceded by start-of-input,
/// - The sigil (`@` / `#`) must be preceded by start-of-input,
/// whitespace, or another chip atom — otherwise this is normal
/// text (e.g. the `/` in `src/main.rs` is not a workflow trigger).
/// text (e.g. the `/` in `src/main.rs` is not a completion trigger).
/// - Whitespace, newlines and chip atoms invalidate an in-flight
/// token — `@foo /` closes the `@foo` candidate as soon as the
/// space lands.
@@ -342,7 +312,6 @@ impl InputBuffer {
let kind = match c {
'@' => Some(protocol::CompletionKind::File),
'#' => Some(protocol::CompletionKind::Knowledge),
'/' => Some(protocol::CompletionKind::Workflow),
_ => None,
};
if let Some(k) = kind {
@@ -511,7 +480,7 @@ impl InputBuffer {
/// Build the typed `Vec<Segment>` sent over the protocol. Adjacent
/// `Atom::Char`s are concatenated into a single `Segment::Text`; each
/// chip atom (`Paste` / `FileRef` / `KnowledgeRef` / `WorkflowInvoke`)
/// chip atom (`Paste` / `FileRef` / `KnowledgeRef` )
/// becomes a standalone `Segment` so that clients re-rendering an
/// `Event::UserMessage` see the same indivisible chip rather than a
/// flattened string.
@@ -547,12 +516,6 @@ impl InputBuffer {
slug: r.slug.clone(),
});
}
Atom::WorkflowInvoke(r) => {
flush_text(&mut buf, &mut out);
out.push(protocol::Segment::WorkflowInvoke {
slug: r.slug.clone(),
});
}
}
}
if !buf.is_empty() {
@@ -993,34 +956,6 @@ mod submit_segments_tests {
assert!(matches!(&segs[0], Segment::Text { content } if content == "see "));
assert!(matches!(&segs[1], Segment::FileRef { path } if path == "src/main.rs"));
}
#[test]
fn knowledge_and_workflow_chips_emit_typed_segments() {
let mut buf = InputBuffer::new();
for c in "#r".chars() {
buf.insert_char(c);
}
buf.replace_with_knowledge_ref(0, "rust-style".into());
buf.insert_char(' ');
for c in "/p".chars() {
buf.insert_char(c);
}
buf.replace_with_workflow_invoke(2, "plan".into());
let segs = buf.submit_segments();
assert_eq!(segs.len(), 3);
match &segs[0] {
Segment::KnowledgeRef { slug } => assert_eq!(slug, "rust-style"),
other => panic!("expected KnowledgeRef, got {other:?}"),
}
match &segs[1] {
Segment::Text { content } => assert_eq!(content, " "),
other => panic!("expected Text, got {other:?}"),
}
match &segs[2] {
Segment::WorkflowInvoke { slug } => assert_eq!(slug, "plan"),
other => panic!("expected WorkflowInvoke, got {other:?}"),
}
}
}
#[cfg(test)]
@@ -1055,7 +990,7 @@ mod completion_prefix_tests {
}
#[test]
fn slash_inside_path_is_not_a_workflow_trigger() {
fn slash_inside_path_is_not_a_completion_trigger() {
// After `@src/m`, the only valid trigger is `@`, not the `/`.
let buf = buf_from("@src/m");
let (kind, start, prefix) = buf.pending_completion_prefix().unwrap();
@@ -1101,14 +1036,6 @@ mod completion_prefix_tests {
assert_eq!(prefix, "abc");
}
#[test]
fn slash_at_start_triggers_workflow_completion() {
let buf = buf_from("/cl");
let (kind, _, prefix) = buf.pending_completion_prefix().unwrap();
assert_eq!(kind, CompletionKind::Workflow);
assert_eq!(prefix, "cl");
}
#[test]
fn newline_before_cursor_invalidates_trigger() {
let buf = buf_from("@a\nbc");
@@ -1307,10 +1234,7 @@ mod word_motion_tests {
for a in &buf.atoms {
match a {
Atom::Char(c) => out.push(*c),
Atom::Paste(_)
| Atom::FileRef(_)
| Atom::KnowledgeRef(_)
| Atom::WorkflowInvoke(_) => out.push_str("<P>"),
Atom::Paste(_) | Atom::FileRef(_) | Atom::KnowledgeRef(_) => out.push_str("<P>"),
}
}
out
-4
View File
@@ -1098,9 +1098,6 @@ fn chip_span_for(seg: &Segment, fallback: Style) -> (Style, String) {
),
Segment::FileRef { path } => (Style::default().fg(Color::Cyan), format!("@{path}")),
Segment::KnowledgeRef { slug } => (Style::default().fg(Color::Green), format!("#{slug}")),
Segment::WorkflowInvoke { slug } => {
(Style::default().fg(Color::Yellow), format!("/{slug}"))
}
Segment::Unknown => (fallback, "[unknown segment]".to_owned()),
}
}
@@ -1116,7 +1113,6 @@ fn segment_display_text(seg: &Segment) -> String {
} => format!("[Clipboard #{id} | {chars} chars, {lines} lines]"),
Segment::FileRef { path } => format!("@{path}"),
Segment::KnowledgeRef { slug } => format!("#{slug}"),
Segment::WorkflowInvoke { slug } => format!("/{slug}"),
Segment::Unknown => "[unknown segment]".to_owned(),
}
}