fix(client): clean up leftover pre-TerminalView dead code and type errors in Workspace.tsx
`npm run test:client` (Vitest/esbuild) doesn't type-check, so several tasks' incomplete cleanup of the old RunConsole-era code in Workspace.tsx (SlashCommand/BUILTIN_SLASH_COMMANDS references, old RunStatus values, a stray `mode` field, a wrong `prompt` vs `initialPrompt` key) went unnoticed through every review until `tsc --noEmit` was run directly. Also fixes a stale `RunStatusPayload.exitCode` read in Tabby's brain.ts and dangling RunStreamPayload/RunInputAckPayload references left in types.ts.
This commit is contained in:
+14
-138
@@ -49,19 +49,9 @@ import type {
|
||||
PermissionMode,
|
||||
RunHandle,
|
||||
RunListResponse,
|
||||
RunMode,
|
||||
RunStartArgs,
|
||||
} from "../lib/api";
|
||||
import type {
|
||||
Session,
|
||||
TranscriptMessage,
|
||||
TranscriptContent,
|
||||
Lane,
|
||||
LaneFeature,
|
||||
LaneCounts,
|
||||
ProofFeature,
|
||||
WSMessage,
|
||||
} from "../lib/types";
|
||||
import type { Session, Lane, LaneFeature, LaneCounts, ProofFeature, WSMessage } from "../lib/types";
|
||||
import { eventBus } from "../lib/eventBus";
|
||||
import { TerminalView } from "../components/run/TerminalView";
|
||||
import { RunSetup } from "../components/run/RunSetup";
|
||||
@@ -109,7 +99,6 @@ export function Workspace() {
|
||||
null
|
||||
);
|
||||
const [cwdSuggestions, setCwdSuggestions] = useState<CwdSuggestion[]>([]);
|
||||
const [slashCommands, setSlashCommands] = useState<any[]>([]);
|
||||
|
||||
// Pre-flight: probe binary + active runs + cwd suggestions + lanes on mount
|
||||
const refreshLanes = useCallback(async () => {
|
||||
@@ -165,24 +154,6 @@ export function Workspace() {
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
// Discover user / project / plugin slash commands. The CLI's built-ins
|
||||
// are appended client-side.
|
||||
Promise.all([api.ccConfig.commands(), api.ccConfig.plugins()])
|
||||
.then(([cmdsResp, pluginsResp]) => {
|
||||
const userProject = cmdsResp.items.map<SlashCommand>((c) => ({
|
||||
name: c.name,
|
||||
description: (c.frontmatter?.description as string | undefined) || c.preview.slice(0, 80),
|
||||
source: c.scope === "project" ? "project" : "user",
|
||||
filePath: c.file,
|
||||
}));
|
||||
const pluginCmds: SlashCommand[] = [];
|
||||
for (const p of pluginsResp.plugins || []) {
|
||||
const cmds = p.contributes?.commands ?? 0;
|
||||
if (!cmds || !p.installPath) continue;
|
||||
}
|
||||
setSlashCommands([...userProject, ...pluginCmds, ...BUILTIN_SLASH_COMMANDS]);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
|
||||
// Subscribe to lane updates from the event bus
|
||||
return eventBus.subscribe((msg: WSMessage) => {
|
||||
@@ -277,10 +248,6 @@ export function Workspace() {
|
||||
setBusy("start");
|
||||
setError(null);
|
||||
try {
|
||||
const transcript = await api.sessions
|
||||
.transcript(item.session_id, { limit: 200 })
|
||||
.catch(() => ({ messages: [] as TranscriptMessage[] }));
|
||||
|
||||
let fetched: RunHandle;
|
||||
|
||||
if (item.cwd) {
|
||||
@@ -301,7 +268,6 @@ export function Workspace() {
|
||||
// Start on the lane with resume
|
||||
const laneStartResult = await api.lanes.action(targetLaneId, "start", {
|
||||
prompt: "",
|
||||
mode: "conversation",
|
||||
model: item.model || undefined,
|
||||
permissionMode: item.permission_mode || undefined,
|
||||
effort: item.effort || undefined,
|
||||
@@ -318,8 +284,8 @@ export function Workspace() {
|
||||
// These runs stay outside the lane system and are cleaned up
|
||||
// by their own expiry, not by lane release.
|
||||
fetched = await api.run.start({
|
||||
prompt: "",
|
||||
mode: "conversation",
|
||||
laneId: 0,
|
||||
initialPrompt: "",
|
||||
cwd: undefined,
|
||||
model: item.model || undefined,
|
||||
permissionMode: item.permission_mode || undefined,
|
||||
@@ -341,47 +307,12 @@ export function Workspace() {
|
||||
[busy, refreshList, t, lanes]
|
||||
);
|
||||
|
||||
// View a past run inline (no spawn). Headless runs are single-shot, so
|
||||
// there's no resume - but the transcript is still worth seeing without
|
||||
// navigating away. Sets a synthetic completed handle so the UI renders
|
||||
// as read-only (no Stop button, no follow-up input - both are gated on isLive).
|
||||
const onViewFromHistory = useCallback(
|
||||
async (item: DashboardRunHistoryItem) => {
|
||||
if (!item.session_id) return;
|
||||
if (busy) return;
|
||||
setError(null);
|
||||
try {
|
||||
const synthetic: RunHandle = {
|
||||
id: item.id,
|
||||
pid: null,
|
||||
mode: item.mode,
|
||||
cwd: item.cwd,
|
||||
model: item.model,
|
||||
permissionMode: item.permission_mode || "acceptEdits",
|
||||
effort: item.effort,
|
||||
prompt: item.prompt_preview || "",
|
||||
argv: [],
|
||||
resumeSessionId: item.resume_session_id,
|
||||
status: item.status,
|
||||
startedAt: new Date(item.started_at).getTime(),
|
||||
endedAt: item.ended_at ? new Date(item.ended_at).getTime() : null,
|
||||
exitCode: item.exit_code,
|
||||
signal: null,
|
||||
error: null,
|
||||
sessionId: item.session_id,
|
||||
envelopeCount: 0,
|
||||
stdoutTail: "",
|
||||
stderrTail: "",
|
||||
};
|
||||
setHandle(synthetic);
|
||||
setResumeSession(null);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : "unknown";
|
||||
setError(t("errors.attachFailed", { message: msg }));
|
||||
}
|
||||
},
|
||||
[busy, t]
|
||||
);
|
||||
// View a past run inline: not implemented in the new TerminalView architecture.
|
||||
// The old RunConsole showed transcripts inline, but TerminalView only shows live
|
||||
// runs. Use SessionDetail page instead.
|
||||
const onViewFromHistory = useCallback(() => {
|
||||
// No-op: feature moved to SessionDetail page
|
||||
}, []);
|
||||
|
||||
const start = useCallback(async () => {
|
||||
if (!prompt.trim() || busy) return;
|
||||
@@ -389,9 +320,6 @@ export function Workspace() {
|
||||
setError(null);
|
||||
try {
|
||||
const effectiveCwd = resumeSession?.cwd || cwd || undefined;
|
||||
// Expand /user-or-project slash commands client-side so the model
|
||||
// receives the rendered template, matching what the CLI does.
|
||||
const expandedPrompt = await maybeExpandSlashCommand(prompt, slashCommands);
|
||||
|
||||
// Determine which lane to use. If no lane is selected or the cwd
|
||||
// doesn't belong to the selected lane, ensure a lane for this cwd first.
|
||||
@@ -438,7 +366,7 @@ export function Workspace() {
|
||||
let laneStartResult;
|
||||
try {
|
||||
laneStartResult = await api.lanes.action(targetLaneId, "start", {
|
||||
prompt: expandedPrompt,
|
||||
prompt: prompt || "",
|
||||
model: model || undefined,
|
||||
permissionMode,
|
||||
resumeSessionId: resumeSession?.id,
|
||||
@@ -543,9 +471,7 @@ export function Workspace() {
|
||||
api.run
|
||||
.list()
|
||||
.then((list) => {
|
||||
const target = list.items.find(
|
||||
(h) => h.sessionId === sid && (h.status === "running" || h.status === "spawning")
|
||||
);
|
||||
const target = list.items.find((h) => h.sessionId === sid && h.status === "running");
|
||||
if (target) {
|
||||
void attachToRun(target.id);
|
||||
} else {
|
||||
@@ -612,10 +538,6 @@ export function Workspace() {
|
||||
setBusy("start");
|
||||
setError(null);
|
||||
try {
|
||||
const expandedPrompt = await maybeExpandSlashCommand(
|
||||
args.initialPrompt || "",
|
||||
slashCommands
|
||||
);
|
||||
const effectiveCwd = args.cwd || undefined;
|
||||
|
||||
if (!effectiveCwd) {
|
||||
@@ -656,7 +578,7 @@ export function Workspace() {
|
||||
let laneStartResult;
|
||||
try {
|
||||
laneStartResult = await api.lanes.action(targetLaneId, "start", {
|
||||
prompt: expandedPrompt,
|
||||
prompt: args.initialPrompt || "",
|
||||
model: args.model || undefined,
|
||||
permissionMode: args.permissionMode,
|
||||
resumeSessionId: args.resumeSessionId,
|
||||
@@ -701,23 +623,9 @@ export function Workspace() {
|
||||
setBusy(null);
|
||||
}
|
||||
},
|
||||
[busy, slashCommands, t, lanes, selectedLaneId, refreshLanes, attachToRun, refreshList]
|
||||
[busy, t, lanes, selectedLaneId, refreshLanes, attachToRun, refreshList, prompt]
|
||||
);
|
||||
|
||||
const stop = useCallback(async () => {
|
||||
if (!handle || busy) return;
|
||||
setBusy("kill");
|
||||
setError(null);
|
||||
try {
|
||||
await api.run.kill(handle.id);
|
||||
} catch (err: unknown) {
|
||||
const m = err instanceof Error ? err.message : "unknown";
|
||||
setError(t("errors.killFailed", { message: m }));
|
||||
} finally {
|
||||
setBusy(null);
|
||||
}
|
||||
}, [handle, busy, t]);
|
||||
|
||||
const newRun = useCallback(() => {
|
||||
setHandle(null);
|
||||
setPrompt("");
|
||||
@@ -725,9 +633,6 @@ export function Workspace() {
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
const status = handle?.status ?? "idle";
|
||||
const isLive = status === "spawning" || status === "running";
|
||||
const hasFinished = status === "completed" || status === "error" || status === "killed";
|
||||
const currentLane = selectedLaneId !== null ? lanes.find((l) => l.id === selectedLaneId) : null;
|
||||
|
||||
// Feature list follows the selected lane, resets the viewer on lane switch.
|
||||
@@ -897,7 +802,7 @@ export function Workspace() {
|
||||
laneCwd={currentLane?.cwd}
|
||||
resumeSession={resumeSession}
|
||||
onResumeSessionChange={setResumeSession}
|
||||
slashCommands={slashCommands}
|
||||
slashCommands={[]}
|
||||
runHistory={runHistory}
|
||||
onResumeFromHistory={onResumeFromHistory}
|
||||
/>
|
||||
@@ -1163,35 +1068,6 @@ export function Workspace() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a user/project/plugin slash command client-side. Reads the command
|
||||
* markdown body via /api/cc-config/file, strips frontmatter, and substitutes
|
||||
* `$ARGUMENTS` with whatever the user typed after the command name. If the
|
||||
* command isn't user-defined (built-in or unknown), returns the original
|
||||
* text unchanged so it still gets sent (the model will see it as text).
|
||||
*/
|
||||
async function maybeExpandSlashCommand(text: string, commands: SlashCommand[]): Promise<string> {
|
||||
const trimmed = text.trimStart();
|
||||
if (!trimmed.startsWith("/")) return text;
|
||||
const m = trimmed.match(/^\/([\w:-]+)(?:\s+([\s\S]*))?$/);
|
||||
if (!m) return text;
|
||||
const [, name, args = ""] = m;
|
||||
const cmd = commands.find((c) => c.name === name);
|
||||
if (!cmd || cmd.source === "builtin" || !cmd.filePath) return text;
|
||||
try {
|
||||
const body = await api.ccConfig.file(cmd.filePath);
|
||||
let content = body.text;
|
||||
// Strip frontmatter if present
|
||||
if (content.startsWith("---")) {
|
||||
const end = content.indexOf("\n---", 3);
|
||||
if (end >= 0) content = content.slice(end + 4).replace(/^\s*\n/, "");
|
||||
}
|
||||
return content.replace(/\$ARGUMENTS/g, args);
|
||||
} catch {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Header ────────────────────────────────────────────────────────────
|
||||
|
||||
function Header({
|
||||
|
||||
Reference in New Issue
Block a user