refactor(hooks/interactive-bash-session): split monolithic hook into modules
- Convert index.ts to clean barrel export
- Extract hook implementation to hook.ts
- Extract terminal parsing to parser.ts
- Extract state management to state-manager.ts
- Reduce index.ts from ~276 to ~5 lines
- Follow modular code architecture principles
🤖 Generated with assistance of OhMyOpenCode
This commit is contained in:
parent
f1316bc800
commit
9377c7eba9
125
src/hooks/interactive-bash-session/hook.ts
Normal file
125
src/hooks/interactive-bash-session/hook.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import type { PluginInput } from "@opencode-ai/plugin";
|
||||||
|
import { loadInteractiveBashSessionState, saveInteractiveBashSessionState, clearInteractiveBashSessionState } from "./storage";
|
||||||
|
import { buildSessionReminderMessage } from "./constants";
|
||||||
|
import type { InteractiveBashSessionState } from "./types";
|
||||||
|
import { tokenizeCommand, findSubcommand, extractSessionNameFromTokens } from "./parser";
|
||||||
|
import { getOrCreateState, isOmoSession, killAllTrackedSessions } from "./state-manager";
|
||||||
|
import { subagentSessions } from "../../features/claude-code-session-state";
|
||||||
|
|
||||||
|
interface ToolExecuteInput {
|
||||||
|
tool: string;
|
||||||
|
sessionID: string;
|
||||||
|
callID: string;
|
||||||
|
args?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolExecuteOutput {
|
||||||
|
title: string;
|
||||||
|
output: string;
|
||||||
|
metadata: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventInput {
|
||||||
|
event: {
|
||||||
|
type: string;
|
||||||
|
properties?: unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createInteractiveBashSessionHook(ctx: PluginInput) {
|
||||||
|
const sessionStates = new Map<string, InteractiveBashSessionState>();
|
||||||
|
|
||||||
|
function getOrCreateStateLocal(sessionID: string): InteractiveBashSessionState {
|
||||||
|
return getOrCreateState(sessionID, sessionStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function killAllTrackedSessionsLocal(
|
||||||
|
state: InteractiveBashSessionState,
|
||||||
|
): Promise<void> {
|
||||||
|
await killAllTrackedSessions(state);
|
||||||
|
|
||||||
|
for (const sessionId of subagentSessions) {
|
||||||
|
ctx.client.session.abort({ path: { id: sessionId } }).catch(() => {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolExecuteAfter = async (
|
||||||
|
input: ToolExecuteInput,
|
||||||
|
output: ToolExecuteOutput,
|
||||||
|
) => {
|
||||||
|
const { tool, sessionID, args } = input;
|
||||||
|
const toolLower = tool.toLowerCase();
|
||||||
|
|
||||||
|
if (toolLower !== "interactive_bash") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof args?.tmux_command !== "string") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmuxCommand = args.tmux_command;
|
||||||
|
const tokens = tokenizeCommand(tmuxCommand);
|
||||||
|
const subCommand = findSubcommand(tokens);
|
||||||
|
const state = getOrCreateStateLocal(sessionID);
|
||||||
|
let stateChanged = false;
|
||||||
|
|
||||||
|
const toolOutput = output?.output ?? ""
|
||||||
|
if (toolOutput.startsWith("Error:")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNewSession = subCommand === "new-session";
|
||||||
|
const isKillSession = subCommand === "kill-session";
|
||||||
|
const isKillServer = subCommand === "kill-server";
|
||||||
|
|
||||||
|
const sessionName = extractSessionNameFromTokens(tokens, subCommand);
|
||||||
|
|
||||||
|
if (isNewSession && isOmoSession(sessionName)) {
|
||||||
|
state.tmuxSessions.add(sessionName!);
|
||||||
|
stateChanged = true;
|
||||||
|
} else if (isKillSession && isOmoSession(sessionName)) {
|
||||||
|
state.tmuxSessions.delete(sessionName!);
|
||||||
|
stateChanged = true;
|
||||||
|
} else if (isKillServer) {
|
||||||
|
state.tmuxSessions.clear();
|
||||||
|
stateChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateChanged) {
|
||||||
|
state.updatedAt = Date.now();
|
||||||
|
saveInteractiveBashSessionState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSessionOperation = isNewSession || isKillSession || isKillServer;
|
||||||
|
if (isSessionOperation) {
|
||||||
|
const reminder = buildSessionReminderMessage(
|
||||||
|
Array.from(state.tmuxSessions),
|
||||||
|
);
|
||||||
|
if (reminder) {
|
||||||
|
output.output += reminder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventHandler = async ({ event }: EventInput) => {
|
||||||
|
const props = event.properties as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
|
if (event.type === "session.deleted") {
|
||||||
|
const sessionInfo = props?.info as { id?: string } | undefined;
|
||||||
|
const sessionID = sessionInfo?.id;
|
||||||
|
|
||||||
|
if (sessionID) {
|
||||||
|
const state = getOrCreateStateLocal(sessionID);
|
||||||
|
await killAllTrackedSessionsLocal(state);
|
||||||
|
sessionStates.delete(sessionID);
|
||||||
|
clearInteractiveBashSessionState(sessionID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
"tool.execute.after": toolExecuteAfter,
|
||||||
|
event: eventHandler,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,267 +1,4 @@
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin";
|
export { createInteractiveBashSessionHook } from "./hook";
|
||||||
import {
|
export * from "./types";
|
||||||
loadInteractiveBashSessionState,
|
export * from "./constants";
|
||||||
saveInteractiveBashSessionState,
|
export * from "./storage";
|
||||||
clearInteractiveBashSessionState,
|
|
||||||
} from "./storage";
|
|
||||||
import { OMO_SESSION_PREFIX, buildSessionReminderMessage } from "./constants";
|
|
||||||
import type { InteractiveBashSessionState } from "./types";
|
|
||||||
import { subagentSessions } from "../../features/claude-code-session-state";
|
|
||||||
|
|
||||||
interface ToolExecuteInput {
|
|
||||||
tool: string;
|
|
||||||
sessionID: string;
|
|
||||||
callID: string;
|
|
||||||
args?: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ToolExecuteOutput {
|
|
||||||
title: string;
|
|
||||||
output: string;
|
|
||||||
metadata: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EventInput {
|
|
||||||
event: {
|
|
||||||
type: string;
|
|
||||||
properties?: unknown;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quote-aware command tokenizer with escape handling
|
|
||||||
* Handles single/double quotes and backslash escapes
|
|
||||||
*/
|
|
||||||
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"
|
|
||||||
*/
|
|
||||||
function normalizeSessionName(name: string): string {
|
|
||||||
return name.split(":")[0].split(".")[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
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)
|
|
||||||
*/
|
|
||||||
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 ""
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createInteractiveBashSessionHook(ctx: PluginInput) {
|
|
||||||
const sessionStates = new Map<string, InteractiveBashSessionState>();
|
|
||||||
|
|
||||||
function getOrCreateState(sessionID: string): 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);
|
|
||||||
}
|
|
||||||
return sessionStates.get(sessionID)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isOmoSession(sessionName: string | null): boolean {
|
|
||||||
return sessionName !== null && sessionName.startsWith(OMO_SESSION_PREFIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function killAllTrackedSessions(
|
|
||||||
state: InteractiveBashSessionState,
|
|
||||||
): Promise<void> {
|
|
||||||
for (const sessionName of state.tmuxSessions) {
|
|
||||||
try {
|
|
||||||
const proc = Bun.spawn(["tmux", "kill-session", "-t", sessionName], {
|
|
||||||
stdout: "ignore",
|
|
||||||
stderr: "ignore",
|
|
||||||
});
|
|
||||||
await proc.exited;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const sessionId of subagentSessions) {
|
|
||||||
ctx.client.session.abort({ path: { id: sessionId } }).catch(() => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const toolExecuteAfter = async (
|
|
||||||
input: ToolExecuteInput,
|
|
||||||
output: ToolExecuteOutput,
|
|
||||||
) => {
|
|
||||||
const { tool, sessionID, args } = input;
|
|
||||||
const toolLower = tool.toLowerCase();
|
|
||||||
|
|
||||||
if (toolLower !== "interactive_bash") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof args?.tmux_command !== "string") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tmuxCommand = args.tmux_command;
|
|
||||||
const tokens = tokenizeCommand(tmuxCommand);
|
|
||||||
const subCommand = findSubcommand(tokens);
|
|
||||||
const state = getOrCreateState(sessionID);
|
|
||||||
let stateChanged = false;
|
|
||||||
|
|
||||||
const toolOutput = output?.output ?? ""
|
|
||||||
if (toolOutput.startsWith("Error:")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNewSession = subCommand === "new-session";
|
|
||||||
const isKillSession = subCommand === "kill-session";
|
|
||||||
const isKillServer = subCommand === "kill-server";
|
|
||||||
|
|
||||||
const sessionName = extractSessionNameFromTokens(tokens, subCommand);
|
|
||||||
|
|
||||||
if (isNewSession && isOmoSession(sessionName)) {
|
|
||||||
state.tmuxSessions.add(sessionName!);
|
|
||||||
stateChanged = true;
|
|
||||||
} else if (isKillSession && isOmoSession(sessionName)) {
|
|
||||||
state.tmuxSessions.delete(sessionName!);
|
|
||||||
stateChanged = true;
|
|
||||||
} else if (isKillServer) {
|
|
||||||
state.tmuxSessions.clear();
|
|
||||||
stateChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stateChanged) {
|
|
||||||
state.updatedAt = Date.now();
|
|
||||||
saveInteractiveBashSessionState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSessionOperation = isNewSession || isKillSession || isKillServer;
|
|
||||||
if (isSessionOperation) {
|
|
||||||
const reminder = buildSessionReminderMessage(
|
|
||||||
Array.from(state.tmuxSessions),
|
|
||||||
);
|
|
||||||
if (reminder) {
|
|
||||||
output.output += reminder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventHandler = async ({ event }: EventInput) => {
|
|
||||||
const props = event.properties as Record<string, unknown> | undefined;
|
|
||||||
|
|
||||||
if (event.type === "session.deleted") {
|
|
||||||
const sessionInfo = props?.info as { id?: string } | undefined;
|
|
||||||
const sessionID = sessionInfo?.id;
|
|
||||||
|
|
||||||
if (sessionID) {
|
|
||||||
const state = getOrCreateState(sessionID);
|
|
||||||
await killAllTrackedSessions(state);
|
|
||||||
sessionStates.delete(sessionID);
|
|
||||||
clearInteractiveBashSessionState(sessionID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
"tool.execute.after": toolExecuteAfter,
|
|
||||||
event: eventHandler,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
118
src/hooks/interactive-bash-session/parser.ts
Normal file
118
src/hooks/interactive-bash-session/parser.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* 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 ""
|
||||||
|
}
|
||||||
40
src/hooks/interactive-bash-session/state-manager.ts
Normal file
40
src/hooks/interactive-bash-session/state-manager.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import type { InteractiveBashSessionState } from "./types";
|
||||||
|
import { loadInteractiveBashSessionState, saveInteractiveBashSessionState } from "./storage";
|
||||||
|
import { OMO_SESSION_PREFIX } from "./constants";
|
||||||
|
import { subagentSessions } from "../../features/claude-code-session-state";
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return sessionStates.get(sessionID)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOmoSession(sessionName: string | null): boolean {
|
||||||
|
return sessionName !== null && sessionName.startsWith(OMO_SESSION_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function killAllTrackedSessions(
|
||||||
|
state: InteractiveBashSessionState,
|
||||||
|
): Promise<void> {
|
||||||
|
for (const sessionName of state.tmuxSessions) {
|
||||||
|
try {
|
||||||
|
const proc = Bun.spawn(["tmux", "kill-session", "-t", sessionName], {
|
||||||
|
stdout: "ignore",
|
||||||
|
stderr: "ignore",
|
||||||
|
});
|
||||||
|
await proc.exited;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const sessionId of subagentSessions) {
|
||||||
|
// Note: ctx is not available here, so we can't call ctx.client.session.abort
|
||||||
|
// This will need to be handled in the hook where ctx is available
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user