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'
state: 'inprogress'
created_at: '2026-07-15T19:02:13Z'
updated_at: '2026-07-15T21:04:19Z'
updated_at: '2026-07-15T21:22:40Z'
assignee: null
queued_by: 'workspace-panel'
queued_at: '2026-07-15T19:54:36Z'

View File

@ -182,4 +182,31 @@ Validation:
- 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`.
#[serde(default)]
pub web: Option<WebConfig>,
/// External Agent Skills (`SKILL.md`) directories to ingest as
/// Workflows. Each entry is a path to a skills *root* (i.e. a
/// External Agent Skills (`SKILL.md`) candidate directories. Each entry
/// is a path to a skills *root* (i.e. a
/// directory whose children are individual `<name>/SKILL.md` skill
/// bundles). Paths are resolved against the manifest's base
/// 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
/// broadcast). The IPC server handles this directly and writes
/// the response straight back to the requesting socket. Empty
/// results for resolvers that are not yet wired up
/// (Knowledge / Workflow).
/// the response straight back to the requesting socket.
ListCompletions {
kind: CompletionKind,
prefix: String,
@ -601,11 +599,7 @@ pub enum AlertSource {
/// Kind of completion requested by `Method::ListCompletions`.
///
/// Mirrors the TUI 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.
/// Mirrors the completion prefix sigils: `@` → `File`, `#` → `Knowledge`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(rename_all = "snake_case")]
@ -616,7 +610,7 @@ pub enum CompletionKind {
/// 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
/// keep a trailing `/` after a directory selection so the user can
/// drill in without re-typing the prefix.

View File

@ -607,8 +607,7 @@ impl App {
/// suggestion" — partial typing like `@README.` followed by
/// Enter should chip when the popup is on `README.md`.
///
/// Files (and Knowledge / Workflow entries, which have no dir
/// concept) chipify here. Directory file entries return `false`
/// Files and Knowledge entries chipify here. Directory file entries return `false`
/// so the caller can fall through to `apply_completion_text`
/// for drill-in — chip-ifying a directory on Enter would strand
/// 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
/// 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.
pub fn set_resident_injection(&mut self, enabled: bool) {
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: {
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", () => {
assertEquals(parseSigilSegments("read @src/main.rs then #memory and /workflow"), [
{ kind: "text", content: "read " },
{ kind: "file_ref", path: "src/main.rs" },
{ kind: "text", content: " then " },
{ kind: "knowledge_ref", slug: "memory" },
{ kind: "text", content: " and /workflow" },
]);
assertEquals(
parseSigilSegments("read @src/main.rs then #memory and /literal"),
[
{ kind: "text", content: "read " },
{ kind: "file_ref", path: "src/main.rs" },
{ kind: "text", content: " then " },
{ kind: "knowledge_ref", slug: "memory" },
{ kind: "text", content: " and /literal" },
],
);
});
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");
assert(help.ok, "help should be accepted");
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", () => {

View File

@ -32,7 +32,7 @@ Deno.test("completionTokenAt detects TUI-style sigils before the cursor", () =>
});
assertEquals(completionTokenAt(":comp", 5)?.kind, "command");
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", () => {
@ -50,5 +50,7 @@ Deno.test("applyCompletion replaces the active token and advances the cursor", (
});
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 = {
sigil: ":" | "@" | "#" | "/";
sigil: ":" | "@" | "#";
kind: ComposerCompletionKind;
start: number;
end: number;
@ -35,7 +35,7 @@ export function completionTokenAt(
): ComposerCompletionToken | null {
const boundedCursor = Math.max(0, Math.min(cursor, value.length));
const before = value.slice(0, boundedCursor);
const match = /(^|\s)([:@#/])([^\s]*)$/.exec(before);
const match = /(^|\s)([:@#])([^\s]*)$/.exec(before);
if (!match) {
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();
return COLON_COMMAND_COMPLETIONS.filter((entry) =>
entry.value.toLowerCase().startsWith(normalized)
@ -65,13 +67,19 @@ export function applyCompletion(
): CompletionApplyResult {
const suffix = entry.is_dir ? "/" : " ";
const replacement = `${token.sigil}${entry.value}${suffix}`;
const restStart = !entry.is_dir && value[token.end] === " " ? token.end + 1 : token.end;
const next = `${value.slice(0, token.start)}${replacement}${value.slice(restStart)}`;
const restStart = !entry.is_dir && value[token.end] === " "
? token.end + 1
: token.end;
const next = `${value.slice(0, token.start)}${replacement}${
value.slice(restStart)
}`;
const cursor = token.start + replacement.length;
return { value: next, cursor };
}
function completionKindForSigil(sigil: ComposerCompletionToken["sigil"]): ComposerCompletionKind {
function completionKindForSigil(
sigil: ComposerCompletionToken["sigil"],
): ComposerCompletionKind {
switch (sigil) {
case ":":
return "command";
@ -79,7 +87,5 @@ function completionKindForSigil(sigil: ComposerCompletionToken["sigil"]): Compos
return "file";
case "#":
return "knowledge";
case "/":
return "workflow";
}
}

View File

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