Revert "Add mesh auto-planning with chat command UX and hardened auth/session behavior"

This reverts commit 16e59b26a6.

# Conflicts:
#	src/auto-reply/reply/commands-mesh.ts
#	src/gateway/server-methods/mesh.ts
#	src/gateway/server-methods/server-methods.test.ts
This commit is contained in:
Peter Steinberger
2026-02-18 02:09:54 +01:00
parent 6dcc052bb4
commit 01672a8f25
14 changed files with 33 additions and 1017 deletions

View File

@@ -17,7 +17,6 @@ import {
handleStatusCommand,
handleWhoamiCommand,
} from "./commands-info.js";
import { handleMeshCommand } from "./commands-mesh.js";
import { handleModelsCommand } from "./commands-models.js";
import { handlePluginCommand } from "./commands-plugin.js";
import {
@@ -53,7 +52,6 @@ export async function handleCommands(params: HandleCommandsParams): Promise<Comm
handleHelpCommand,
handleCommandsListCommand,
handleStatusCommand,
handleMeshCommand,
handleAllowlistCommand,
handleApproveCommand,
handleContextCommand,

View File

@@ -1,351 +0,0 @@
import { callGateway } from "../../gateway/call.js";
import { logVerbose } from "../../globals.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
import type { CommandHandler } from "./commands-types.js";
type MeshPlanShape = {
planId: string;
goal: string;
createdAt: number;
steps: Array<{ id: string; name?: string; prompt: string; dependsOn?: string[] }>;
};
type CachedMeshPlan = { plan: MeshPlanShape; createdAt: number };
type ParsedMeshCommand =
| { ok: true; action: "help" }
| { ok: true; action: "run" | "plan"; target: string }
| { ok: true; action: "status"; runId: string }
| { ok: true; action: "retry"; runId: string; stepIds?: string[] }
| { ok: false; message: string }
| null;
const meshPlanCache = new Map<string, CachedMeshPlan>();
const MAX_CACHED_MESH_PLANS = 200;
function trimMeshPlanCache() {
if (meshPlanCache.size <= MAX_CACHED_MESH_PLANS) {
return;
}
const oldest = [...meshPlanCache.entries()]
.toSorted((a, b) => a[1].createdAt - b[1].createdAt)
.slice(0, meshPlanCache.size - MAX_CACHED_MESH_PLANS);
for (const [key] of oldest) {
meshPlanCache.delete(key);
}
}
function parseMeshCommand(commandBody: string): ParsedMeshCommand {
const trimmed = commandBody.trim();
if (!/^\/mesh\b/i.test(trimmed)) {
return null;
}
const rest = trimmed.replace(/^\/mesh\b:?/i, "").trim();
if (!rest || /^help$/i.test(rest)) {
return { ok: true, action: "help" };
}
const tokens = rest.split(/\s+/).filter(Boolean);
if (tokens.length === 0) {
return { ok: true, action: "help" };
}
const actionCandidate = tokens[0]?.toLowerCase() ?? "";
const explicitAction =
actionCandidate === "run" ||
actionCandidate === "plan" ||
actionCandidate === "status" ||
actionCandidate === "retry"
? actionCandidate
: null;
if (!explicitAction) {
// Shorthand: `/mesh <goal>` => auto plan + run
return { ok: true, action: "run", target: rest };
}
const actionArgs = rest.slice(tokens[0]?.length ?? 0).trim();
if (explicitAction === "plan" || explicitAction === "run") {
if (!actionArgs) {
return { ok: false, message: `Usage: /mesh ${explicitAction} <goal>` };
}
return { ok: true, action: explicitAction, target: actionArgs };
}
if (explicitAction === "status") {
if (!actionArgs) {
return { ok: false, message: "Usage: /mesh status <runId>" };
}
return { ok: true, action: "status", runId: actionArgs.split(/\s+/)[0] };
}
// retry
const argsTokens = actionArgs.split(/\s+/).filter(Boolean);
if (argsTokens.length === 0) {
return { ok: false, message: "Usage: /mesh retry <runId> [step1,step2,...]" };
}
const runId = argsTokens[0];
const stepArg = argsTokens.slice(1).join(" ").trim();
const stepIds =
stepArg.length > 0
? stepArg
.split(",")
.map((entry) => entry.trim())
.filter(Boolean)
: undefined;
return { ok: true, action: "retry", runId, stepIds };
}
function cacheKeyForPlan(params: Parameters<CommandHandler>[0], planId: string) {
const sender = params.command.senderId ?? "unknown";
const channel = params.command.channel || "unknown";
return `${channel}:${sender}:${planId}`;
}
function putCachedPlan(params: Parameters<CommandHandler>[0], plan: MeshPlanShape) {
meshPlanCache.set(cacheKeyForPlan(params, plan.planId), { plan, createdAt: Date.now() });
trimMeshPlanCache();
}
function getCachedPlan(
params: Parameters<CommandHandler>[0],
planId: string,
): MeshPlanShape | null {
return meshPlanCache.get(cacheKeyForPlan(params, planId))?.plan ?? null;
}
function looksLikeMeshPlanId(value: string) {
return /^mesh-plan-[a-z0-9-]+$/i.test(value.trim());
}
function resolveMeshCommandBody(params: Parameters<CommandHandler>[0]) {
return (
params.ctx.BodyForCommands ??
params.ctx.CommandBody ??
params.ctx.RawBody ??
params.ctx.Body ??
params.command.commandBodyNormalized
);
}
function formatPlanSummary(plan: {
goal: string;
steps: Array<{ id: string; name?: string; prompt: string; dependsOn?: string[] }>;
}) {
const lines = [`🕸️ Mesh Plan`, `Goal: ${plan.goal}`, "", `Steps (${plan.steps.length}):`];
for (const step of plan.steps) {
const dependsOn = Array.isArray(step.dependsOn) && step.dependsOn.length > 0;
const depLine = dependsOn ? ` (depends on: ${step.dependsOn?.join(", ")})` : "";
lines.push(`- ${step.id}${step.name ? `${step.name}` : ""}${depLine}`);
lines.push(` ${step.prompt}`);
}
return lines.join("\n");
}
function formatRunSummary(payload: {
runId: string;
status: string;
stats?: {
total?: number;
succeeded?: number;
failed?: number;
skipped?: number;
running?: number;
pending?: number;
};
}) {
const stats = payload.stats ?? {};
return [
`🕸️ Mesh Run`,
`Run: ${payload.runId}`,
`Status: ${payload.status}`,
`Steps: total=${stats.total ?? 0}, ok=${stats.succeeded ?? 0}, failed=${stats.failed ?? 0}, skipped=${stats.skipped ?? 0}, running=${stats.running ?? 0}, pending=${stats.pending ?? 0}`,
].join("\n");
}
function meshUsageText() {
return [
"🕸️ Mesh command",
"Usage:",
"- /mesh <goal> (auto plan + run)",
"- /mesh plan <goal>",
"- /mesh run <goal|mesh-plan-id>",
"- /mesh status <runId>",
"- /mesh retry <runId> [step1,step2,...]",
].join("\n");
}
function resolveMeshClientLabel(params: Parameters<CommandHandler>[0]) {
const channel = params.command.channel;
const sender = params.command.senderId ?? "unknown";
return `Chat mesh (${channel}:${sender})`;
}
export const handleMeshCommand: CommandHandler = async (params, allowTextCommands) => {
if (!allowTextCommands) {
return null;
}
const parsed = parseMeshCommand(resolveMeshCommandBody(params));
if (!parsed) {
return null;
}
if (!params.command.isAuthorizedSender) {
logVerbose(
`Ignoring /mesh from unauthorized sender: ${params.command.senderId || "<unknown>"}`,
);
return { shouldContinue: false };
}
if (!parsed.ok) {
return { shouldContinue: false, reply: { text: parsed.message } };
}
if (parsed.action === "help") {
return { shouldContinue: false, reply: { text: meshUsageText() } };
}
const clientDisplayName = resolveMeshClientLabel(params);
const commonGateway = {
clientName: GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
clientDisplayName,
mode: GATEWAY_CLIENT_MODES.BACKEND,
} as const;
try {
if (parsed.action === "plan") {
const planResp = await callGateway<{
plan: MeshPlanShape;
order?: string[];
source?: string;
}>({
method: "mesh.plan.auto",
params: {
goal: parsed.target,
agentId: params.agentId ?? "main",
},
...commonGateway,
});
putCachedPlan(params, planResp.plan);
const sourceLine = planResp.source ? `\nPlanner source: ${planResp.source}` : "";
return {
shouldContinue: false,
reply: {
text: `${formatPlanSummary(planResp.plan)}${sourceLine}\n\nRun exact plan: /mesh run ${planResp.plan.planId}`,
},
};
}
if (parsed.action === "run") {
let runPlan: MeshPlanShape;
if (looksLikeMeshPlanId(parsed.target)) {
const cached = getCachedPlan(params, parsed.target.trim());
if (!cached) {
return {
shouldContinue: false,
reply: {
text: `Plan ${parsed.target.trim()} not found in this chat.\nCreate one first: /mesh plan <goal>`,
},
};
}
runPlan = cached;
} else {
const planResp = await callGateway<{
plan: MeshPlanShape;
order?: string[];
source?: string;
}>({
method: "mesh.plan.auto",
params: {
goal: parsed.target,
agentId: params.agentId ?? "main",
},
...commonGateway,
});
putCachedPlan(params, planResp.plan);
runPlan = planResp.plan;
}
const runResp = await callGateway<{
runId: string;
status: string;
stats?: {
total?: number;
succeeded?: number;
failed?: number;
skipped?: number;
running?: number;
pending?: number;
};
}>({
method: "mesh.run",
params: {
plan: runPlan,
},
...commonGateway,
});
return {
shouldContinue: false,
reply: {
text: `${formatPlanSummary(runPlan)}\n\n${formatRunSummary(runResp)}`,
},
};
}
if (parsed.action === "status") {
const statusResp = await callGateway<{
runId: string;
status: string;
stats?: {
total?: number;
succeeded?: number;
failed?: number;
skipped?: number;
running?: number;
pending?: number;
};
}>({
method: "mesh.status",
params: { runId: parsed.runId },
...commonGateway,
});
return {
shouldContinue: false,
reply: { text: formatRunSummary(statusResp) },
};
}
if (parsed.action === "retry") {
const retryResp = await callGateway<{
runId: string;
status: string;
stats?: {
total?: number;
succeeded?: number;
failed?: number;
skipped?: number;
running?: number;
pending?: number;
};
}>({
method: "mesh.retry",
params: {
runId: parsed.runId,
...(parsed.stepIds && parsed.stepIds.length > 0 ? { stepIds: parsed.stepIds } : {}),
},
...commonGateway,
});
return {
shouldContinue: false,
reply: { text: `🔁 Retry submitted\n${formatRunSummary(retryResp)}` },
};
}
return null;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return {
shouldContinue: false,
reply: {
text: `❌ Mesh command failed: ${message}`,
},
};
}
};

View File

@@ -288,154 +288,6 @@ describe("/approve command", () => {
});
});
describe("/mesh command", () => {
beforeEach(() => {
vi.clearAllMocks();
callGatewayMock.mockReset();
});
it("shows usage for bare /mesh", async () => {
const cfg = {
commands: { text: true },
channels: { whatsapp: { allowFrom: ["*"] } },
} as OpenClawConfig;
const params = buildParams("/mesh", cfg);
const result = await handleCommands(params);
expect(result.shouldContinue).toBe(false);
expect(result.reply?.text).toContain("Mesh command");
expect(result.reply?.text).toContain("/mesh run <goal|mesh-plan-id>");
expect(callGatewayMock).not.toHaveBeenCalled();
});
it("runs auto plan + run for /mesh <goal>", async () => {
const cfg = {
commands: { text: true },
channels: { whatsapp: { allowFrom: ["*"] } },
} as OpenClawConfig;
const params = buildParams("/mesh build a landing animation", cfg);
callGatewayMock
.mockResolvedValueOnce({
plan: {
planId: "mesh-plan-1",
goal: "build a landing animation",
createdAt: Date.now(),
steps: [
{ id: "design", prompt: "Design animation" },
{ id: "mobile-test", prompt: "Test mobile", dependsOn: ["design"] },
],
},
order: ["design", "mobile-test"],
source: "llm",
})
.mockResolvedValueOnce({
runId: "mesh-run-1",
status: "completed",
stats: { total: 2, succeeded: 2, failed: 0, skipped: 0, running: 0, pending: 0 },
});
const result = await handleCommands(params);
expect(result.shouldContinue).toBe(false);
expect(result.reply?.text).toContain("Mesh Plan");
expect(result.reply?.text).toContain("Mesh Run");
expect(callGatewayMock).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
method: "mesh.plan.auto",
params: expect.objectContaining({
goal: "build a landing animation",
}),
}),
);
expect(callGatewayMock).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
method: "mesh.run",
}),
);
});
it("returns status via /mesh status <runId>", async () => {
const cfg = {
commands: { text: true },
channels: { whatsapp: { allowFrom: ["*"] } },
} as OpenClawConfig;
const params = buildParams("/mesh status mesh-run-77", cfg);
callGatewayMock.mockResolvedValueOnce({
runId: "mesh-run-77",
status: "failed",
stats: { total: 3, succeeded: 1, failed: 1, skipped: 1, running: 0, pending: 0 },
});
const result = await handleCommands(params);
expect(result.shouldContinue).toBe(false);
expect(result.reply?.text).toContain("Run: mesh-run-77");
expect(result.reply?.text).toContain("Status: failed");
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "mesh.status",
params: { runId: "mesh-run-77" },
}),
);
});
it("runs a previously planned mesh plan id without re-planning", async () => {
const cfg = {
commands: { text: true },
channels: { whatsapp: { allowFrom: ["*"] } },
} as OpenClawConfig;
const planParams = buildParams("/mesh plan Build Hero Animation", cfg);
callGatewayMock.mockResolvedValueOnce({
plan: {
planId: "mesh-plan-abc",
goal: "Build Hero Animation",
createdAt: Date.now(),
steps: [{ id: "design", prompt: "Design hero animation" }],
},
order: ["design"],
source: "llm",
});
const planResult = await handleCommands(planParams);
expect(planResult.shouldContinue).toBe(false);
expect(planResult.reply?.text).toContain("Run exact plan: /mesh run mesh-plan-abc");
expect(callGatewayMock).toHaveBeenCalledTimes(1);
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "mesh.plan.auto",
params: expect.objectContaining({
goal: "Build Hero Animation",
}),
}),
);
callGatewayMock.mockReset();
callGatewayMock.mockResolvedValueOnce({
runId: "mesh-run-abc",
status: "completed",
stats: { total: 1, succeeded: 1, failed: 0, skipped: 0, running: 0, pending: 0 },
});
const runParams = buildParams("/mesh run mesh-plan-abc", cfg);
const runResult = await handleCommands(runParams);
expect(runResult.shouldContinue).toBe(false);
expect(callGatewayMock).toHaveBeenCalledTimes(1);
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "mesh.run",
params: expect.objectContaining({
plan: expect.objectContaining({
planId: "mesh-plan-abc",
goal: "Build Hero Animation",
}),
}),
}),
);
});
});
describe("/compact command", () => {
beforeEach(() => {
vi.clearAllMocks();