



























11import crypto from "node:crypto";
22import { resolveAgentDir, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
3+import type { MessagePresentation } from "openclaw/plugin-sdk/interactive-runtime";
34import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
45import type { PluginCommandContext, PluginCommandResult } from "openclaw/plugin-sdk/plugin-entry";
56import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -240,12 +241,144 @@ export function resetCodexDiagnosticsFeedbackStateForTests(): void {
240241pendingCodexDiagnosticsConfirmationTokensByScope.clear();
241242}
242243244+type CodexPickerButton = { label: string; command: string };
245+246+function buildPickerPresentation(title: string, prompt: string, buttons: CodexPickerButton[]) {
247+return {
248+ title,
249+blocks: [
250+{ type: "text", text: prompt },
251+{
252+type: "buttons",
253+buttons: buttons.map((button) => ({
254+label: button.label,
255+value: button.command,
256+})),
257+},
258+],
259+} satisfies MessagePresentation;
260+}
261+262+/**
263+ * No-arg `/codex` picker. Core owns the native command tree; channels render
264+ * the portable buttons as inline controls when their transport can.
265+ */
266+function buildCodexSubcommandPickerReply(): PluginCommandResult {
267+const verbs: CodexPickerButton[] = [
268+{ label: "plugins", command: "/codex plugins menu" },
269+{ label: "permissions", command: "/codex permissions menu" },
270+{ label: "fast", command: "/codex fast menu" },
271+{ label: "computer-use", command: "/codex computer-use menu" },
272+{ label: "account", command: "/codex account" },
273+{ label: "help", command: "/codex help" },
274+];
275+276+const fallbackTextLines = [
277+"Codex commands. Pick a category or type:",
278+"",
279+ ...verbs.map((v, i) => ` ${i + 1}. ${v.command}`),
280+"",
281+"Tap 'help' (or type /codex help) for the full list of typeable verbs",
282+"including threads, mcp, binding, detach, skills, resume, bind, steer,",
283+"model, diagnostics, compact, review, computer-use.",
284+"",
285+"Top-level shortcuts cover everyday operations: /status, /fast, /help, /stop, /models.",
286+];
287+288+return {
289+text: fallbackTextLines.join("\n"),
290+presentation: buildPickerPresentation("Codex commands", "Pick a Codex subcommand:", verbs),
291+};
292+}
293+294+/** Sub-picker for `/codex fast menu` (on / off / status). */
295+function buildCodexFastMenuReply(): PluginCommandResult {
296+const modes = ["on", "off", "status"] as const;
297+const buttons: CodexPickerButton[] = [
298+ ...modes.map((mode) => ({ label: mode, command: `/codex fast ${mode}` })),
299+{ label: "back", command: "/codex" },
300+];
301+const fallbackTextLines = [
302+"Codex fast mode. Pick one or type /codex fast <mode>:",
303+"",
304+ ...modes.map((m, i) => ` ${i + 1}. /codex fast ${m}`),
305+"",
306+"Type '/codex' to go back to the main menu.",
307+];
308+return {
309+text: fallbackTextLines.join("\n"),
310+presentation: buildPickerPresentation("Codex fast mode", "Pick a Codex fast mode:", buttons),
311+};
312+}
313+314+/** Sub-picker for `/codex permissions menu` (default / yolo / status). */
315+function buildCodexPermissionsMenuReply(): PluginCommandResult {
316+const modes = ["default", "yolo", "status"] as const;
317+const buttons: CodexPickerButton[] = [
318+ ...modes.map((mode) => ({ label: mode, command: `/codex permissions ${mode}` })),
319+{ label: "back", command: "/codex" },
320+];
321+const fallbackTextLines = [
322+"Codex permissions. Pick one or type /codex permissions <mode>:",
323+"",
324+ ...modes.map((m, i) => ` ${i + 1}. /codex permissions ${m}`),
325+"",
326+"Type '/codex' to go back to the main menu.",
327+];
328+return {
329+text: fallbackTextLines.join("\n"),
330+presentation: buildPickerPresentation(
331+"Codex permissions",
332+"Pick a Codex permissions mode:",
333+buttons,
334+),
335+};
336+}
337+338+/** Sub-picker for `/codex computer-use menu` (status / install). */
339+function buildCodexComputerUseMenuReply(): PluginCommandResult {
340+const actions = ["status", "install"] as const;
341+const buttons: CodexPickerButton[] = [
342+ ...actions.map((action) => ({
343+label: action,
344+command: `/codex computer-use ${action}`,
345+})),
346+{ label: "back", command: "/codex" },
347+];
348+const fallbackTextLines = [
349+"Codex computer-use. Pick one or type /codex computer-use <action>:",
350+"",
351+ ...actions.map((a, i) => ` ${i + 1}. /codex computer-use ${a}`),
352+"",
353+"Flag-driven invocations (--source, --marketplace-path, --marketplace) are not in the picker. Type '/codex computer-use' or read '/codex help' for the full surface.",
354+"",
355+"Type '/codex' to go back to the main menu.",
356+];
357+return {
358+text: fallbackTextLines.join("\n"),
359+presentation: buildPickerPresentation(
360+"Codex computer-use",
361+"Pick a Codex computer-use action:",
362+buttons,
363+),
364+};
365+}
366+367+/** Returns true when the rest-args are exactly `["menu"]` (case-insensitive). */
368+function isMenuVerb(rest: readonly string[]): boolean {
369+return rest.length === 1 && (rest[0] ?? "").trim().toLowerCase() === "menu";
370+}
371+243372export async function handleCodexSubcommand(
244373ctx: PluginCommandContext,
245374options: { pluginConfig?: unknown; deps?: Partial<CodexCommandDeps> },
246375): Promise<PluginCommandResult> {
247376const deps: CodexCommandDeps = { ...defaultCodexCommandDeps, ...options.deps };
248-const [subcommand = "status", ...rest] = splitArgs(ctx.args);
377+const args = splitArgs(ctx.args);
378+if (args.length === 0) {
379+return buildCodexSubcommandPickerReply();
380+}
381+const [subcommand = "status", ...rest] = args;
249382const normalized = subcommand.toLowerCase();
250383if (normalized === "help") {
251384return { text: buildHelp() };
@@ -321,9 +454,15 @@ export async function handleCodexSubcommand(
321454return { text: await setConversationModel(deps, ctx, options.pluginConfig, rest) };
322455}
323456if (normalized === "fast") {
457+if (isMenuVerb(rest)) {
458+return buildCodexFastMenuReply();
459+}
324460return { text: await setConversationFastMode(deps, ctx, options.pluginConfig, rest) };
325461}
326462if (normalized === "permissions") {
463+if (isMenuVerb(rest)) {
464+return buildCodexPermissionsMenuReply();
465+}
327466return { text: await setConversationPermissions(deps, ctx, options.pluginConfig, rest) };
328467}
329468if (normalized === "compact") {
@@ -360,6 +499,9 @@ export async function handleCodexSubcommand(
360499);
361500}
362501if (normalized === "computer-use" || normalized === "computeruse") {
502+if (isMenuVerb(rest)) {
503+return buildCodexComputerUseMenuReply();
504+}
363505return {
364506text: await handleComputerUseCommand(deps, options.pluginConfig, rest),
365507};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。