fix: remove workflow completion surface

This commit is contained in:
Keisuke Hirata 2026-07-16 06:22:41 +09:00
parent d30dca2d99
commit 0b56052a44
No known key found for this signature in database
10 changed files with 73 additions and 36 deletions

View File

@ -2,7 +2,7 @@
title: 'Remove workflow tracking and workflow resources' title: 'Remove workflow tracking and workflow resources'
state: 'inprogress' state: 'inprogress'
created_at: '2026-07-15T19:02:13Z' created_at: '2026-07-15T19:02:13Z'
updated_at: '2026-07-15T21:04:19Z' updated_at: '2026-07-15T21:22:40Z'
assignee: null assignee: null
queued_by: 'workspace-panel' queued_by: 'workspace-panel'
queued_at: '2026-07-15T19:54:36Z' queued_at: '2026-07-15T19:54:36Z'

View File

@ -182,4 +182,31 @@ Validation:
- nix build .#yoi --no-link -> exit 0 - nix build .#yoi --no-link -> exit 0
---
<!-- event: implementation_report author: hare at: 2026-07-15T21:22:40Z -->
## Implementation report
Follow-up fix for focused re-review blocker.
Summary:
- Removed `/` as a web console completion sigil; composer completions now only recognize commands, file refs, and Knowledge refs.
- Removed `workflow` from the web console completion result type and route-side completion response typing.
- Updated web completion tests so slash text remains plain text/no completion token.
- Removed stale Workflow completion/resolver wording from protocol and nearby comments.
- Did not add hidden Workflow compatibility or broad Skills replacement.
Validation:
- rg "ActiveWorkflow|active_workflow|Active workflow" -> no matches (exit 1)
- focused completion-surface rg for workflow_invoke / workflow completion kind / slash Workflow resolver wording / resources/workflows / .yoi/workflow / Resident workflows -> no matches (exit 1)
- focused web/protocol rg "kind.*workflow" -> no matches (exit 1); remaining project-wide "kind: workflow" mentions are only legacy persisted SystemItem ignore tests from the bounded compatibility path.
- git diff --check -> exit 0
- cd web/workspace && deno task check && deno task test -> exit 0
- cargo test -p yoi --tests -> exit 0
- cargo check -p yoi -> exit 0
- yoi ticket doctor -> exit 0
- nix build .#yoi --no-link -> exit 0
--- ---

View File

@ -83,8 +83,8 @@ pub struct WorkerManifest {
/// `[feature.web].enabled = true`. /// `[feature.web].enabled = true`.
#[serde(default)] #[serde(default)]
pub web: Option<WebConfig>, pub web: Option<WebConfig>,
/// External Agent Skills (`SKILL.md`) directories to ingest as /// External Agent Skills (`SKILL.md`) candidate directories. Each entry
/// Workflows. Each entry is a path to a skills *root* (i.e. a /// is a path to a skills *root* (i.e. a
/// directory whose children are individual `<name>/SKILL.md` skill /// directory whose children are individual `<name>/SKILL.md` skill
/// bundles). Paths are resolved against the manifest's base /// bundles). Paths are resolved against the manifest's base
/// directory like other path fields. Absent ⇒ no skills loaded; /// directory like other path fields. Absent ⇒ no skills loaded;

View File

@ -70,9 +70,7 @@ pub enum Method {
/// ///
/// Reply is sent on the same socket as `Event::Completions` (not /// Reply is sent on the same socket as `Event::Completions` (not
/// broadcast). The IPC server handles this directly and writes /// broadcast). The IPC server handles this directly and writes
/// the response straight back to the requesting socket. Empty /// the response straight back to the requesting socket.
/// results for resolvers that are not yet wired up
/// (Knowledge / Workflow).
ListCompletions { ListCompletions {
kind: CompletionKind, kind: CompletionKind,
prefix: String, prefix: String,
@ -601,11 +599,7 @@ pub enum AlertSource {
/// Kind of completion requested by `Method::ListCompletions`. /// Kind of completion requested by `Method::ListCompletions`.
/// ///
/// Mirrors the TUI prefix sigils: `@` → `File`, `#` → `Knowledge`, /// Mirrors the completion prefix sigils: `@` → `File`, `#` → `Knowledge`.
/// `/` → `Workflow`. Knowledge and Workflow resolvers are currently
/// stubs (always reply with empty `entries`); the wire shape is
/// nailed down here so the TUI side can ship without waiting for
/// the memory / workflow tickets.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -616,7 +610,7 @@ pub enum CompletionKind {
/// One candidate returned in `Event::Completions::entries`. /// One candidate returned in `Event::Completions::entries`.
/// ///
/// `value` is a path (file kind) or a slug (knowledge / workflow). /// `value` is a path (file kind) or a Knowledge slug.
/// `is_dir` is meaningful only for the file kind — it lets the TUI /// `is_dir` is meaningful only for the file kind — it lets the TUI
/// keep a trailing `/` after a directory selection so the user can /// keep a trailing `/` after a directory selection so the user can
/// drill in without re-typing the prefix. /// drill in without re-typing the prefix.

View File

@ -607,8 +607,7 @@ impl App {
/// suggestion" — partial typing like `@README.` followed by /// suggestion" — partial typing like `@README.` followed by
/// Enter should chip when the popup is on `README.md`. /// Enter should chip when the popup is on `README.md`.
/// ///
/// Files (and Knowledge / Workflow entries, which have no dir /// Files and Knowledge entries chipify here. Directory file entries return `false`
/// concept) chipify here. Directory file entries return `false`
/// so the caller can fall through to `apply_completion_text` /// so the caller can fall through to `apply_completion_text`
/// for drill-in — chip-ifying a directory on Enter would strand /// for drill-in — chip-ifying a directory on Enter would strand
/// the user with no way to inspect children. /// the user with no way to inspect children.

View File

@ -833,7 +833,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
/// ///
/// Default `true`: normal Workers may expose each resident section according /// Default `true`: normal Workers may expose each resident section according
/// to its own gate and manifest settings. Internal disposable workers set /// to its own gate and manifest settings. Internal disposable workers set
/// this to `false` so summary, Knowledge, and Workflow residency are all /// this to `false` so summary and Knowledge residency are both
/// suppressed while explicit tools remain available. /// suppressed while explicit tools remain available.
pub fn set_resident_injection(&mut self, enabled: bool) { pub fn set_resident_injection(&mut self, enabled: bool) {
self.inject_resident_summary = enabled; self.inject_resident_summary = enabled;

View File

@ -1,4 +1,7 @@
import { buildComposerRequest, parseSigilSegments } from "./composer-command.ts"; import {
buildComposerRequest,
parseSigilSegments,
} from "./composer-command.ts";
declare const Deno: { declare const Deno: {
test(name: string, fn: () => void): void; test(name: string, fn: () => void): void;
@ -19,13 +22,16 @@ function assertEquals<T>(actual: T, expected: T): void {
} }
Deno.test("parseSigilSegments converts TUI-style references", () => { Deno.test("parseSigilSegments converts TUI-style references", () => {
assertEquals(parseSigilSegments("read @src/main.rs then #memory and /workflow"), [ assertEquals(
{ kind: "text", content: "read " }, parseSigilSegments("read @src/main.rs then #memory and /literal"),
{ kind: "file_ref", path: "src/main.rs" }, [
{ kind: "text", content: " then " }, { kind: "text", content: "read " },
{ kind: "knowledge_ref", slug: "memory" }, { kind: "file_ref", path: "src/main.rs" },
{ kind: "text", content: " and /workflow" }, { kind: "text", content: " then " },
]); { kind: "knowledge_ref", slug: "memory" },
{ kind: "text", content: " and /literal" },
],
);
}); });
Deno.test("buildComposerRequest sends user segments when sigils are present", () => { Deno.test("buildComposerRequest sends user segments when sigils are present", () => {
@ -51,7 +57,10 @@ Deno.test("buildComposerRequest parses colon commands", () => {
const help = buildComposerRequest(":help compact"); const help = buildComposerRequest(":help compact");
assert(help.ok, "help should be accepted"); assert(help.ok, "help should be accepted");
assertEquals(help.request, undefined); assertEquals(help.request, undefined);
assert(help.notice?.includes(":compact"), "help should return a local notice"); assert(
help.notice?.includes(":compact"),
"help should return a local notice",
);
}); });
Deno.test("buildComposerRequest rejects invalid colon commands", () => { Deno.test("buildComposerRequest rejects invalid colon commands", () => {

View File

@ -32,7 +32,7 @@ Deno.test("completionTokenAt detects TUI-style sigils before the cursor", () =>
}); });
assertEquals(completionTokenAt(":comp", 5)?.kind, "command"); assertEquals(completionTokenAt(":comp", 5)?.kind, "command");
assertEquals(completionTokenAt("ask #mem", 8)?.kind, "knowledge"); assertEquals(completionTokenAt("ask #mem", 8)?.kind, "knowledge");
assertEquals(completionTokenAt("run /work", 9)?.kind, "workflow"); assertEquals(completionTokenAt("run /work", 9), null);
}); });
Deno.test("applyCompletion replaces the active token and advances the cursor", () => { Deno.test("applyCompletion replaces the active token and advances the cursor", () => {
@ -50,5 +50,7 @@ Deno.test("applyCompletion replaces the active token and advances the cursor", (
}); });
Deno.test("localCommandCompletions filters colon commands", () => { Deno.test("localCommandCompletions filters colon commands", () => {
assertEquals(localCommandCompletions("com").map((entry) => entry.value), ["compact"]); assertEquals(localCommandCompletions("com").map((entry) => entry.value), [
"compact",
]);
}); });

View File

@ -1,7 +1,7 @@
export type ComposerCompletionKind = "command" | "file" | "knowledge" | "workflow"; export type ComposerCompletionKind = "command" | "file" | "knowledge";
export type ComposerCompletionToken = { export type ComposerCompletionToken = {
sigil: ":" | "@" | "#" | "/"; sigil: ":" | "@" | "#";
kind: ComposerCompletionKind; kind: ComposerCompletionKind;
start: number; start: number;
end: number; end: number;
@ -35,7 +35,7 @@ export function completionTokenAt(
): ComposerCompletionToken | null { ): ComposerCompletionToken | null {
const boundedCursor = Math.max(0, Math.min(cursor, value.length)); const boundedCursor = Math.max(0, Math.min(cursor, value.length));
const before = value.slice(0, boundedCursor); const before = value.slice(0, boundedCursor);
const match = /(^|\s)([:@#/])([^\s]*)$/.exec(before); const match = /(^|\s)([:@#])([^\s]*)$/.exec(before);
if (!match) { if (!match) {
return null; return null;
} }
@ -51,7 +51,9 @@ export function completionTokenAt(
}; };
} }
export function localCommandCompletions(prefix: string): ComposerCompletionEntry[] { export function localCommandCompletions(
prefix: string,
): ComposerCompletionEntry[] {
const normalized = prefix.toLowerCase(); const normalized = prefix.toLowerCase();
return COLON_COMMAND_COMPLETIONS.filter((entry) => return COLON_COMMAND_COMPLETIONS.filter((entry) =>
entry.value.toLowerCase().startsWith(normalized) entry.value.toLowerCase().startsWith(normalized)
@ -65,13 +67,19 @@ export function applyCompletion(
): CompletionApplyResult { ): CompletionApplyResult {
const suffix = entry.is_dir ? "/" : " "; const suffix = entry.is_dir ? "/" : " ";
const replacement = `${token.sigil}${entry.value}${suffix}`; const replacement = `${token.sigil}${entry.value}${suffix}`;
const restStart = !entry.is_dir && value[token.end] === " " ? token.end + 1 : token.end; const restStart = !entry.is_dir && value[token.end] === " "
const next = `${value.slice(0, token.start)}${replacement}${value.slice(restStart)}`; ? token.end + 1
: token.end;
const next = `${value.slice(0, token.start)}${replacement}${
value.slice(restStart)
}`;
const cursor = token.start + replacement.length; const cursor = token.start + replacement.length;
return { value: next, cursor }; return { value: next, cursor };
} }
function completionKindForSigil(sigil: ComposerCompletionToken["sigil"]): ComposerCompletionKind { function completionKindForSigil(
sigil: ComposerCompletionToken["sigil"],
): ComposerCompletionKind {
switch (sigil) { switch (sigil) {
case ":": case ":":
return "command"; return "command";
@ -79,7 +87,5 @@ function completionKindForSigil(sigil: ComposerCompletionToken["sigil"]): Compos
return "file"; return "file";
case "#": case "#":
return "knowledge"; return "knowledge";
case "/":
return "workflow";
} }
} }

View File

@ -46,7 +46,7 @@
} }
type WorkerCompletionsResult = { type WorkerCompletionsResult = {
kind: "file" | "knowledge" | "workflow"; kind: "file" | "knowledge";
prefix: string; prefix: string;
entries: ComposerCompletionEntry[]; entries: ComposerCompletionEntry[];
diagnostics: Diagnostic[]; diagnostics: Diagnostic[];