Merge pull request #4040 from code-yeongyu/cleanup/typescript-ai-slop-20260515

Refactor TypeScript cleanup patterns
This commit is contained in:
YeonGyu-Kim
2026-05-15 16:42:10 +09:00
committed by GitHub
17 changed files with 81 additions and 186 deletions
+4 -7
View File
@@ -2,7 +2,7 @@ import type { PluginInput } from "@opencode-ai/plugin";
import { saveInteractiveBashSessionState, clearInteractiveBashSessionState } from "./storage";
import { buildSessionReminderMessage } from "./constants";
import type { InteractiveBashSessionState } from "./types";
import { tokenizeCommand, findSubcommand, extractSessionNameFromTokens } from "./parser";
import { parseTmuxCommand } from "./tmux-command-parser";
import { getOrCreateState, isOmoSession, killAllTrackedSessions } from "./state-manager";
import { subagentSessions } from "../../features/claude-code-session-state";
import { resolveSessionEventID } from "../../shared/event-session-id";
@@ -60,8 +60,7 @@ export function createInteractiveBashSessionHook(ctx: PluginInput) {
}
const tmuxCommand = args.tmux_command;
const tokens = tokenizeCommand(tmuxCommand);
const subCommand = findSubcommand(tokens);
const { subCommand, sessionName } = parseTmuxCommand(tmuxCommand);
const state = getOrCreateStateLocal(sessionID);
let stateChanged = false;
@@ -74,13 +73,11 @@ export function createInteractiveBashSessionHook(ctx: PluginInput) {
const isKillSession = subCommand === "kill-session";
const isKillServer = subCommand === "kill-server";
const sessionName = extractSessionNameFromTokens(tokens, subCommand);
if (isNewSession && isOmoSession(sessionName)) {
state.tmuxSessions.add(sessionName!);
state.tmuxSessions.add(sessionName);
stateChanged = true;
} else if (isKillSession && isOmoSession(sessionName)) {
state.tmuxSessions.delete(sessionName!);
state.tmuxSessions.delete(sessionName);
stateChanged = true;
} else if (isKillServer) {
state.tmuxSessions.clear();
@@ -1,118 +0,0 @@
/**
* Quote-aware command tokenizer with escape handling
* Handles single/double quotes and backslash escapes
*/
export function tokenizeCommand(cmd: string): string[] {
const tokens: string[] = []
let current = ""
let inQuote = false
let quoteChar = ""
let escaped = false
for (let i = 0; i < cmd.length; i++) {
const char = cmd[i]
if (escaped) {
current += char
escaped = false
continue
}
if (char === "\\") {
escaped = true
continue
}
if ((char === "'" || char === '"') && !inQuote) {
inQuote = true
quoteChar = char
} else if (char === quoteChar && inQuote) {
inQuote = false
quoteChar = ""
} else if (char === " " && !inQuote) {
if (current) {
tokens.push(current)
current = ""
}
} else {
current += char
}
}
if (current) tokens.push(current)
return tokens
}
/**
* Normalize session name by stripping :window and .pane suffixes
* e.g., "omo-x:1" -> "omo-x", "omo-x:1.2" -> "omo-x"
*/
export function normalizeSessionName(name: string): string {
return name.split(":")[0].split(".")[0]
}
export function findFlagValue(tokens: string[], flag: string): string | null {
for (let i = 0; i < tokens.length - 1; i++) {
if (tokens[i] === flag) return tokens[i + 1]
}
return null
}
/**
* Extract session name from tokens, considering the subCommand
* For new-session: prioritize -s over -t
* For other commands: use -t
*/
export function extractSessionNameFromTokens(tokens: string[], subCommand: string): string | null {
if (subCommand === "new-session") {
const sFlag = findFlagValue(tokens, "-s")
if (sFlag) return normalizeSessionName(sFlag)
const tFlag = findFlagValue(tokens, "-t")
if (tFlag) return normalizeSessionName(tFlag)
} else {
const tFlag = findFlagValue(tokens, "-t")
if (tFlag) return normalizeSessionName(tFlag)
}
return null
}
/**
* Find the tmux subcommand from tokens, skipping global options.
* tmux allows global options before the subcommand:
* e.g., `tmux -L socket-name new-session -s omo-x`
* Global options with args: -L, -S, -f, -c, -T
* Standalone flags: -C, -v, -V, etc.
* Special: -- (end of options marker)
*/
export function findSubcommand(tokens: string[]): string {
// Options that require an argument: -L, -S, -f, -c, -T
const globalOptionsWithArgs = new Set(["-L", "-S", "-f", "-c", "-T"])
let i = 0
while (i < tokens.length) {
const token = tokens[i]
// Handle end of options marker
if (token === "--") {
// Next token is the subcommand
return tokens[i + 1] ?? ""
}
if (globalOptionsWithArgs.has(token)) {
// Skip the option and its argument
i += 2
continue
}
if (token.startsWith("-")) {
// Skip standalone flags like -C, -v, -V
i++
continue
}
// Found the subcommand
return token
}
return ""
}
@@ -2,21 +2,25 @@ import type { InteractiveBashSessionState } from "./types";
import { loadInteractiveBashSessionState } from "./storage";
import { OMO_SESSION_PREFIX } from "./constants";
import { spawnWithWindowsHide } from "../../shared/spawn-with-windows-hide";
import { log } from "../../shared/logger";
export function getOrCreateState(sessionID: string, sessionStates: Map<string, InteractiveBashSessionState>): InteractiveBashSessionState {
if (!sessionStates.has(sessionID)) {
const persisted = loadInteractiveBashSessionState(sessionID);
const state: InteractiveBashSessionState = persisted ?? {
sessionID,
tmuxSessions: new Set<string>(),
updatedAt: Date.now(),
};
sessionStates.set(sessionID, state);
const existing = sessionStates.get(sessionID);
if (existing) {
return existing;
}
return sessionStates.get(sessionID)!;
const persisted = loadInteractiveBashSessionState(sessionID);
const state: InteractiveBashSessionState = persisted ?? {
sessionID,
tmuxSessions: new Set<string>(),
updatedAt: Date.now(),
};
sessionStates.set(sessionID, state);
return state;
}
export function isOmoSession(sessionName: string | null): boolean {
export function isOmoSession(sessionName: string | null): sessionName is string {
return sessionName !== null && sessionName.startsWith(OMO_SESSION_PREFIX);
}
@@ -30,6 +34,11 @@ export async function killAllTrackedSessions(
stderr: "ignore",
});
await proc.exited;
} catch {}
} catch (error) {
log("[interactive-bash-session] failed to kill tracked tmux session", {
error: error instanceof Error ? error.message : String(error),
sessionName,
});
}
}
}
+2 -2
View File
@@ -189,8 +189,8 @@ export function createReadImageResizerHook(_ctx: PluginInput) {
}
}
if (attachmentsToRemove.length > 0) {
const rawAttachments = outputRecord.attachments as unknown[]
if (attachmentsToRemove.length > 0 && Array.isArray(outputRecord.attachments)) {
const rawAttachments = outputRecord.attachments
for (const toRemove of attachmentsToRemove) {
const removeIndex = rawAttachments.indexOf(toRemove)
if (removeIndex !== -1) {
@@ -62,7 +62,7 @@ export async function readMessagesFromSDK(
): Promise<StoredMessageMeta[]> {
try {
const response = await client.session.messages({ path: { id: sessionID } })
const data = normalizeSDKResponse(response, [] as unknown[], {
const data = normalizeSDKResponse<unknown[]>(response, [], {
preferResponseOnMissingData: true,
})
if (!Array.isArray(data)) return []
@@ -1190,16 +1190,9 @@ describe("todo-continuation-enforcer", () => {
// then - continuation injected (non-abort errors don't block)
expect(promptCalls.length).toBe(1)
}, { timeout: 15000 })
// ============================================================
// API-BASED ABORT DETECTION TESTS
// These tests verify that abort is detected by checking
// the last assistant message's error field via session.messages API
// ============================================================
test("should skip injection when last assistant message has MessageAbortedError", async () => {
// given - session where last assistant message was aborted
@@ -1673,11 +1666,9 @@ describe("todo-continuation-enforcer", () => {
expect(promptCalls[0].model).toEqual({ providerID: "openai", modelID: "gpt-5.4" })
})
// ============================================================
// COMPACTION AGENT FILTERING TESTS
// These tests verify that compaction agent messages are filtered
// when resolving agent info, preventing infinite continuation loops
// ============================================================
test("should skip injection while the latest message is from the compaction agent", async () => {
// given - session where the latest activity is still the compaction assistant turn
@@ -2102,11 +2093,9 @@ describe("todo-continuation-enforcer", () => {
expect(promptCalls).toHaveLength(1)
}, { timeout: 20000 })
// ============================================================
// TOKEN-LIMIT ERROR DETECTION TESTS (#2462)
// These tests verify that the enforcer does NOT retry continuation
// when the model returns a token-limit / context-length error.
// ============================================================
test("should stop continuation when session.error carries a ContextLengthError", async () => {
// given - session with incomplete todos