mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
UI: align slash commands with session tree scope
This commit is contained in:
@@ -810,6 +810,7 @@ export function listSessionsFromStore(params: {
|
||||
const model = resolvedModel.model ?? DEFAULT_MODEL;
|
||||
return {
|
||||
key,
|
||||
spawnedBy: entry?.spawnedBy,
|
||||
entry,
|
||||
kind: classifySessionKey(key, entry),
|
||||
label: entry?.label,
|
||||
|
||||
@@ -15,6 +15,7 @@ export type GatewaySessionsDefaults = {
|
||||
|
||||
export type GatewaySessionRow = {
|
||||
key: string;
|
||||
spawnedBy?: string;
|
||||
kind: "direct" | "group" | "global" | "unknown";
|
||||
label?: string;
|
||||
displayName?: string;
|
||||
|
||||
@@ -3,11 +3,13 @@ import type { GatewayBrowserClient } from "../gateway.ts";
|
||||
import type { GatewaySessionRow } from "../types.ts";
|
||||
import { executeSlashCommand } from "./slash-command-executor.ts";
|
||||
|
||||
function row(key: string): GatewaySessionRow {
|
||||
function row(key: string, overrides?: Partial<GatewaySessionRow>): GatewaySessionRow {
|
||||
return {
|
||||
key,
|
||||
spawnedBy: overrides?.spawnedBy,
|
||||
kind: "direct",
|
||||
updatedAt: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,8 +20,11 @@ describe("executeSlashCommand /kill", () => {
|
||||
return {
|
||||
sessions: [
|
||||
row("main"),
|
||||
row("agent:main:subagent:one"),
|
||||
row("agent:main:subagent:parent:subagent:child"),
|
||||
row("agent:main:subagent:one", { spawnedBy: "main" }),
|
||||
row("agent:main:subagent:parent", { spawnedBy: "main" }),
|
||||
row("agent:main:subagent:parent:subagent:child", {
|
||||
spawnedBy: "agent:main:subagent:parent",
|
||||
}),
|
||||
row("agent:other:main"),
|
||||
],
|
||||
};
|
||||
@@ -37,12 +42,15 @@ describe("executeSlashCommand /kill", () => {
|
||||
"all",
|
||||
);
|
||||
|
||||
expect(result.content).toBe("Aborted 2 sub-agent sessions.");
|
||||
expect(result.content).toBe("Aborted 3 sub-agent sessions.");
|
||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.list", {});
|
||||
expect(request).toHaveBeenNthCalledWith(2, "chat.abort", {
|
||||
sessionKey: "agent:main:subagent:one",
|
||||
});
|
||||
expect(request).toHaveBeenNthCalledWith(3, "chat.abort", {
|
||||
sessionKey: "agent:main:subagent:parent",
|
||||
});
|
||||
expect(request).toHaveBeenNthCalledWith(4, "chat.abort", {
|
||||
sessionKey: "agent:main:subagent:parent:subagent:child",
|
||||
});
|
||||
});
|
||||
@@ -52,9 +60,9 @@ describe("executeSlashCommand /kill", () => {
|
||||
if (method === "sessions.list") {
|
||||
return {
|
||||
sessions: [
|
||||
row("agent:main:subagent:one"),
|
||||
row("agent:main:subagent:two"),
|
||||
row("agent:other:subagent:three"),
|
||||
row("agent:main:subagent:one", { spawnedBy: "agent:main:main" }),
|
||||
row("agent:main:subagent:two", { spawnedBy: "agent:main:main" }),
|
||||
row("agent:other:subagent:three", { spawnedBy: "agent:other:main" }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -86,9 +94,11 @@ describe("executeSlashCommand /kill", () => {
|
||||
if (method === "sessions.list") {
|
||||
return {
|
||||
sessions: [
|
||||
row("agent:main:subagent:parent"),
|
||||
row("agent:main:subagent:parent:subagent:child"),
|
||||
row("agent:main:subagent:sibling"),
|
||||
row("agent:main:subagent:parent", { spawnedBy: "agent:main:main" }),
|
||||
row("agent:main:subagent:parent:subagent:child", {
|
||||
spawnedBy: "agent:main:subagent:parent",
|
||||
}),
|
||||
row("agent:main:subagent:sibling", { spawnedBy: "agent:main:main" }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -116,7 +126,10 @@ describe("executeSlashCommand /kill", () => {
|
||||
const request = vi.fn(async (method: string, _payload?: unknown) => {
|
||||
if (method === "sessions.list") {
|
||||
return {
|
||||
sessions: [row("agent:main:subagent:one"), row("agent:main:subagent:two")],
|
||||
sessions: [
|
||||
row("agent:main:subagent:one", { spawnedBy: "agent:main:main" }),
|
||||
row("agent:main:subagent:two", { spawnedBy: "agent:main:main" }),
|
||||
],
|
||||
};
|
||||
}
|
||||
if (method === "chat.abort") {
|
||||
@@ -148,9 +161,9 @@ describe("executeSlashCommand /kill", () => {
|
||||
return {
|
||||
sessions: [
|
||||
row("main"),
|
||||
row("agent:main:subagent:one"),
|
||||
row("agent:main:subagent:two"),
|
||||
row("agent:other:subagent:three"),
|
||||
row("agent:main:subagent:one", { spawnedBy: "agent:main:main" }),
|
||||
row("agent:main:subagent:two", { spawnedBy: "agent:main:main" }),
|
||||
row("agent:other:subagent:three", { spawnedBy: "agent:other:main" }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -176,4 +189,128 @@ describe("executeSlashCommand /kill", () => {
|
||||
sessionKey: "agent:main:subagent:two",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not abort unrelated same-agent subagents from another root session", async () => {
|
||||
const request = vi.fn(async (method: string, _payload?: unknown) => {
|
||||
if (method === "sessions.list") {
|
||||
return {
|
||||
sessions: [
|
||||
row("agent:main:main"),
|
||||
row("agent:main:subagent:mine", { spawnedBy: "agent:main:main" }),
|
||||
row("agent:main:subagent:mine:subagent:child", {
|
||||
spawnedBy: "agent:main:subagent:mine",
|
||||
}),
|
||||
row("agent:main:subagent:other-root", {
|
||||
spawnedBy: "agent:main:discord:dm:alice",
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
if (method === "chat.abort") {
|
||||
return { ok: true, aborted: true };
|
||||
}
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
});
|
||||
|
||||
const result = await executeSlashCommand(
|
||||
{ request } as unknown as GatewayBrowserClient,
|
||||
"agent:main:main",
|
||||
"kill",
|
||||
"all",
|
||||
);
|
||||
|
||||
expect(result.content).toBe("Aborted 2 sub-agent sessions.");
|
||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.list", {});
|
||||
expect(request).toHaveBeenNthCalledWith(2, "chat.abort", {
|
||||
sessionKey: "agent:main:subagent:mine",
|
||||
});
|
||||
expect(request).toHaveBeenNthCalledWith(3, "chat.abort", {
|
||||
sessionKey: "agent:main:subagent:mine:subagent:child",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("executeSlashCommand directives", () => {
|
||||
it("reports the current thinking level for bare /think", async () => {
|
||||
const request = vi.fn(async (method: string, _payload?: unknown) => {
|
||||
if (method === "sessions.list") {
|
||||
return {
|
||||
sessions: [
|
||||
row("agent:main:main", {
|
||||
modelProvider: "openai",
|
||||
model: "gpt-4.1-mini",
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
if (method === "models.list") {
|
||||
return {
|
||||
models: [{ id: "gpt-4.1-mini", provider: "openai", reasoning: true }],
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
});
|
||||
|
||||
const result = await executeSlashCommand(
|
||||
{ request } as unknown as GatewayBrowserClient,
|
||||
"agent:main:main",
|
||||
"think",
|
||||
"",
|
||||
);
|
||||
|
||||
expect(result.content).toBe(
|
||||
"Current thinking level: low.\nOptions: off, minimal, low, medium, high, adaptive.",
|
||||
);
|
||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.list", {});
|
||||
expect(request).toHaveBeenNthCalledWith(2, "models.list", {});
|
||||
});
|
||||
|
||||
it("accepts minimal and xhigh thinking levels", async () => {
|
||||
const request = vi.fn().mockResolvedValueOnce({ ok: true }).mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const minimal = await executeSlashCommand(
|
||||
{ request } as unknown as GatewayBrowserClient,
|
||||
"agent:main:main",
|
||||
"think",
|
||||
"minimal",
|
||||
);
|
||||
const xhigh = await executeSlashCommand(
|
||||
{ request } as unknown as GatewayBrowserClient,
|
||||
"agent:main:main",
|
||||
"think",
|
||||
"xhigh",
|
||||
);
|
||||
|
||||
expect(minimal.content).toBe("Thinking level set to **minimal**.");
|
||||
expect(xhigh.content).toBe("Thinking level set to **xhigh**.");
|
||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.patch", {
|
||||
key: "agent:main:main",
|
||||
thinkingLevel: "minimal",
|
||||
});
|
||||
expect(request).toHaveBeenNthCalledWith(2, "sessions.patch", {
|
||||
key: "agent:main:main",
|
||||
thinkingLevel: "xhigh",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports the current verbose level for bare /verbose", async () => {
|
||||
const request = vi.fn(async (method: string, _payload?: unknown) => {
|
||||
if (method === "sessions.list") {
|
||||
return {
|
||||
sessions: [row("agent:main:main", { verboseLevel: "full" })],
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
});
|
||||
|
||||
const result = await executeSlashCommand(
|
||||
{ request } as unknown as GatewayBrowserClient,
|
||||
"agent:main:main",
|
||||
"verbose",
|
||||
"",
|
||||
);
|
||||
|
||||
expect(result.content).toBe("Current verbose level: full.\nOptions: on, full, off.");
|
||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.list", {});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,14 @@
|
||||
*/
|
||||
|
||||
import type { ModelCatalogEntry } from "../../../../src/agents/model-catalog.js";
|
||||
import { resolveThinkingDefault } from "../../../../src/agents/model-selection.js";
|
||||
import {
|
||||
formatThinkingLevels,
|
||||
normalizeThinkLevel,
|
||||
normalizeVerboseLevel,
|
||||
} from "../../../../src/auto-reply/thinking.js";
|
||||
import type { HealthSummary } from "../../../../src/commands/health.js";
|
||||
import type { OpenClawConfig } from "../../../../src/config/config.js";
|
||||
import {
|
||||
DEFAULT_AGENT_ID,
|
||||
DEFAULT_MAIN_KEY,
|
||||
@@ -166,18 +173,31 @@ async function executeThink(
|
||||
sessionKey: string,
|
||||
args: string,
|
||||
): Promise<SlashCommandResult> {
|
||||
const valid = ["off", "low", "medium", "high"];
|
||||
const level = args.trim().toLowerCase();
|
||||
|
||||
if (!level) {
|
||||
return {
|
||||
content: `Usage: \`/think <${valid.join("|")}>\``,
|
||||
};
|
||||
const rawLevel = args.trim();
|
||||
if (!rawLevel) {
|
||||
try {
|
||||
const { session, models } = await loadThinkingCommandState(client, sessionKey);
|
||||
return {
|
||||
content: formatDirectiveOptions(
|
||||
`Current thinking level: ${resolveCurrentThinkingLevel(session, models)}.`,
|
||||
formatThinkingLevels(session?.modelProvider, session?.model),
|
||||
),
|
||||
};
|
||||
} catch (err) {
|
||||
return { content: `Failed to get thinking level: ${String(err)}` };
|
||||
}
|
||||
}
|
||||
if (!valid.includes(level)) {
|
||||
return {
|
||||
content: `Invalid thinking level \`${level}\`. Choose: ${valid.map((v) => `\`${v}\``).join(", ")}`,
|
||||
};
|
||||
|
||||
const level = normalizeThinkLevel(rawLevel);
|
||||
if (!level) {
|
||||
try {
|
||||
const session = await loadCurrentSession(client, sessionKey);
|
||||
return {
|
||||
content: `Unrecognized thinking level "${rawLevel}". Valid levels: ${formatThinkingLevels(session?.modelProvider, session?.model)}.`,
|
||||
};
|
||||
} catch (err) {
|
||||
return { content: `Failed to validate thinking level: ${String(err)}` };
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -196,17 +216,25 @@ async function executeVerbose(
|
||||
sessionKey: string,
|
||||
args: string,
|
||||
): Promise<SlashCommandResult> {
|
||||
const valid = ["on", "off", "full"];
|
||||
const level = args.trim().toLowerCase();
|
||||
const rawLevel = args.trim();
|
||||
if (!rawLevel) {
|
||||
try {
|
||||
const session = await loadCurrentSession(client, sessionKey);
|
||||
return {
|
||||
content: formatDirectiveOptions(
|
||||
`Current verbose level: ${normalizeVerboseLevel(session?.verboseLevel) ?? "off"}.`,
|
||||
"on, full, off",
|
||||
),
|
||||
};
|
||||
} catch (err) {
|
||||
return { content: `Failed to get verbose level: ${String(err)}` };
|
||||
}
|
||||
}
|
||||
|
||||
const level = normalizeVerboseLevel(rawLevel);
|
||||
if (!level) {
|
||||
return {
|
||||
content: `Usage: \`/verbose <${valid.join("|")}>\``,
|
||||
};
|
||||
}
|
||||
if (!valid.includes(level)) {
|
||||
return {
|
||||
content: `Invalid verbose level \`${level}\`. Choose: ${valid.map((v) => `\`${v}\``).join(", ")}`,
|
||||
content: `Unrecognized verbose level "${rawLevel}". Valid levels: off, on, full.`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -354,6 +382,7 @@ function resolveKillTargets(
|
||||
const currentAgentId =
|
||||
currentParsed?.agentId ??
|
||||
(normalizedCurrentSessionKey === DEFAULT_MAIN_KEY ? DEFAULT_AGENT_ID : undefined);
|
||||
const sessionIndex = buildSessionIndex(sessions);
|
||||
for (const session of sessions) {
|
||||
const key = session?.key?.trim();
|
||||
if (!key || !isSubagentSessionKey(key)) {
|
||||
@@ -364,6 +393,7 @@ function resolveKillTargets(
|
||||
const belongsToCurrentSession = isWithinCurrentSessionSubtree(
|
||||
normalizedKey,
|
||||
normalizedCurrentSessionKey,
|
||||
sessionIndex,
|
||||
currentAgentId,
|
||||
parsed?.agentId,
|
||||
);
|
||||
@@ -384,19 +414,125 @@ function resolveKillTargets(
|
||||
function isWithinCurrentSessionSubtree(
|
||||
candidateSessionKey: string,
|
||||
currentSessionKey: string,
|
||||
sessionIndex: Map<string, GatewaySessionRow>,
|
||||
currentAgentId: string | undefined,
|
||||
candidateAgentId: string | undefined,
|
||||
): boolean {
|
||||
if (!currentAgentId || candidateAgentId !== currentAgentId) {
|
||||
return false;
|
||||
}
|
||||
if (!isSubagentSessionKey(currentSessionKey)) {
|
||||
return true;
|
||||
|
||||
const currentAliases = resolveEquivalentSessionKeys(currentSessionKey, currentAgentId);
|
||||
const seen = new Set<string>();
|
||||
let parentSessionKey = normalizeSessionKey(sessionIndex.get(candidateSessionKey)?.spawnedBy);
|
||||
while (parentSessionKey && !seen.has(parentSessionKey)) {
|
||||
if (currentAliases.has(parentSessionKey)) {
|
||||
return true;
|
||||
}
|
||||
seen.add(parentSessionKey);
|
||||
parentSessionKey = normalizeSessionKey(sessionIndex.get(parentSessionKey)?.spawnedBy);
|
||||
}
|
||||
return (
|
||||
candidateSessionKey === currentSessionKey ||
|
||||
candidateSessionKey.startsWith(`${currentSessionKey}:subagent:`)
|
||||
);
|
||||
|
||||
// Older gateways may not include spawnedBy on session rows yet; keep prefix
|
||||
// matching for nested subagent sessions as a compatibility fallback.
|
||||
return isSubagentSessionKey(currentSessionKey)
|
||||
? candidateSessionKey.startsWith(`${currentSessionKey}:subagent:`)
|
||||
: false;
|
||||
}
|
||||
|
||||
function buildSessionIndex(sessions: GatewaySessionRow[]): Map<string, GatewaySessionRow> {
|
||||
const index = new Map<string, GatewaySessionRow>();
|
||||
for (const session of sessions) {
|
||||
const normalizedKey = normalizeSessionKey(session?.key);
|
||||
if (!normalizedKey) {
|
||||
continue;
|
||||
}
|
||||
index.set(normalizedKey, session);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
function normalizeSessionKey(key?: string | null): string | undefined {
|
||||
const normalized = key?.trim().toLowerCase();
|
||||
return normalized || undefined;
|
||||
}
|
||||
|
||||
function resolveEquivalentSessionKeys(
|
||||
currentSessionKey: string,
|
||||
currentAgentId: string | undefined,
|
||||
): Set<string> {
|
||||
const keys = new Set<string>([currentSessionKey]);
|
||||
if (currentAgentId === DEFAULT_AGENT_ID) {
|
||||
const canonicalDefaultMain = `agent:${DEFAULT_AGENT_ID}:main`;
|
||||
if (currentSessionKey === DEFAULT_MAIN_KEY) {
|
||||
keys.add(canonicalDefaultMain);
|
||||
} else if (currentSessionKey === canonicalDefaultMain) {
|
||||
keys.add(DEFAULT_MAIN_KEY);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
function formatDirectiveOptions(text: string, options: string): string {
|
||||
return `${text}\nOptions: ${options}.`;
|
||||
}
|
||||
|
||||
async function loadCurrentSession(
|
||||
client: GatewayBrowserClient,
|
||||
sessionKey: string,
|
||||
): Promise<GatewaySessionRow | undefined> {
|
||||
const sessions = await client.request<SessionsListResult>("sessions.list", {});
|
||||
const normalizedSessionKey = normalizeSessionKey(sessionKey);
|
||||
const currentAgentId =
|
||||
parseAgentSessionKey(normalizedSessionKey ?? "")?.agentId ??
|
||||
(normalizedSessionKey === DEFAULT_MAIN_KEY ? DEFAULT_AGENT_ID : undefined);
|
||||
const aliases = normalizedSessionKey
|
||||
? resolveEquivalentSessionKeys(normalizedSessionKey, currentAgentId)
|
||||
: new Set<string>();
|
||||
return sessions?.sessions?.find((session: GatewaySessionRow) => {
|
||||
const key = normalizeSessionKey(session.key);
|
||||
return key ? aliases.has(key) : false;
|
||||
});
|
||||
}
|
||||
|
||||
async function loadThinkingCommandState(client: GatewayBrowserClient, sessionKey: string) {
|
||||
const [sessions, models] = await Promise.all([
|
||||
client.request<SessionsListResult>("sessions.list", {}),
|
||||
client.request<{ models: ModelCatalogEntry[] }>("models.list", {}),
|
||||
]);
|
||||
const normalizedSessionKey = normalizeSessionKey(sessionKey);
|
||||
const currentAgentId =
|
||||
parseAgentSessionKey(normalizedSessionKey ?? "")?.agentId ??
|
||||
(normalizedSessionKey === DEFAULT_MAIN_KEY ? DEFAULT_AGENT_ID : undefined);
|
||||
const aliases = normalizedSessionKey
|
||||
? resolveEquivalentSessionKeys(normalizedSessionKey, currentAgentId)
|
||||
: new Set<string>();
|
||||
return {
|
||||
session: sessions?.sessions?.find((session: GatewaySessionRow) => {
|
||||
const key = normalizeSessionKey(session.key);
|
||||
return key ? aliases.has(key) : false;
|
||||
}),
|
||||
models: models?.models ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
function resolveCurrentThinkingLevel(
|
||||
session: GatewaySessionRow | undefined,
|
||||
models: ModelCatalogEntry[],
|
||||
): string {
|
||||
const persisted = normalizeThinkLevel(session?.thinkingLevel);
|
||||
if (persisted) {
|
||||
return persisted;
|
||||
}
|
||||
if (!session?.modelProvider || !session.model) {
|
||||
return "off";
|
||||
}
|
||||
return resolveThinkingDefault({
|
||||
cfg: {} as OpenClawConfig,
|
||||
provider: session.modelProvider,
|
||||
model: session.model,
|
||||
catalog: models,
|
||||
});
|
||||
}
|
||||
|
||||
function fmtTokens(n: number): string {
|
||||
|
||||
26
ui/src/ui/chat/slash-commands.node.test.ts
Normal file
26
ui/src/ui/chat/slash-commands.node.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseSlashCommand } from "./slash-commands.ts";
|
||||
|
||||
describe("parseSlashCommand", () => {
|
||||
it("parses commands with an optional colon separator", () => {
|
||||
expect(parseSlashCommand("/think: high")).toMatchObject({
|
||||
command: { name: "think" },
|
||||
args: "high",
|
||||
});
|
||||
expect(parseSlashCommand("/think:high")).toMatchObject({
|
||||
command: { name: "think" },
|
||||
args: "high",
|
||||
});
|
||||
expect(parseSlashCommand("/help:")).toMatchObject({
|
||||
command: { name: "help" },
|
||||
args: "",
|
||||
});
|
||||
});
|
||||
|
||||
it("still parses space-delimited commands", () => {
|
||||
expect(parseSlashCommand("/verbose full")).toMatchObject({
|
||||
command: { name: "verbose" },
|
||||
args: "full",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -77,7 +77,7 @@ export const SLASH_COMMANDS: SlashCommandDef[] = [
|
||||
icon: "brain",
|
||||
category: "model",
|
||||
executeLocal: true,
|
||||
argOptions: ["off", "low", "medium", "high"],
|
||||
argOptions: ["off", "minimal", "low", "medium", "high", "xhigh", "adaptive"],
|
||||
},
|
||||
{
|
||||
name: "verbose",
|
||||
@@ -192,7 +192,7 @@ export type ParsedSlashCommand = {
|
||||
|
||||
/**
|
||||
* Parse a message as a slash command. Returns null if it doesn't match.
|
||||
* Supports `/command` and `/command args...`.
|
||||
* Supports `/command`, `/command args...`, and `/command: args...`.
|
||||
*/
|
||||
export function parseSlashCommand(text: string): ParsedSlashCommand | null {
|
||||
const trimmed = text.trim();
|
||||
@@ -200,9 +200,14 @@ export function parseSlashCommand(text: string): ParsedSlashCommand | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const spaceIdx = trimmed.indexOf(" ");
|
||||
const name = spaceIdx === -1 ? trimmed.slice(1) : trimmed.slice(1, spaceIdx);
|
||||
const args = spaceIdx === -1 ? "" : trimmed.slice(spaceIdx + 1).trim();
|
||||
const body = trimmed.slice(1);
|
||||
const firstSeparator = body.search(/[\s:]/u);
|
||||
const name = firstSeparator === -1 ? body : body.slice(0, firstSeparator);
|
||||
let remainder = firstSeparator === -1 ? "" : body.slice(firstSeparator).trimStart();
|
||||
if (remainder.startsWith(":")) {
|
||||
remainder = remainder.slice(1).trimStart();
|
||||
}
|
||||
const args = remainder.trim();
|
||||
|
||||
if (!name) {
|
||||
return null;
|
||||
|
||||
@@ -395,6 +395,7 @@ export type AgentsFilesSetResult = {
|
||||
|
||||
export type GatewaySessionRow = {
|
||||
key: string;
|
||||
spawnedBy?: string;
|
||||
kind: "direct" | "group" | "global" | "unknown";
|
||||
label?: string;
|
||||
displayName?: string;
|
||||
|
||||
Reference in New Issue
Block a user