@openclaw/codex 2026.7.2-beta.4 → 2026.7.2-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-server-policy-BO3jYBkv.js → app-server-policy-BSaO2JIT.js} +1 -1
- package/dist/{attempt-notifications-DdmnBMUp.js → attempt-notifications-AP-a-Q5k.js} +1 -1
- package/dist/{bounded-turn-CaQL96kg.js → bounded-turn-D2ZAgZVz.js} +54 -24
- package/dist/{capabilities-CLICvTxf.js → capabilities-BFDTVYr4.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-DgADqnHa.js → command-formatters-C336eRVS.js} +2 -2
- package/dist/{command-handlers-l_lVPNlB.js → command-handlers-DAHUSry9.js} +1233 -1222
- package/dist/{compact-CC5KhBzX.js → compact-D_uvHurS.js} +8 -8
- package/dist/{computer-use-Ip1-QGa-.js → computer-use-CYEc8U3w.js} +123 -115
- package/dist/{conversation-control-sNPsbTCP.js → conversation-control-xsIeXbo4.js} +6 -6
- package/dist/doctor-contract-api.js +24 -86
- package/dist/{dynamic-tools-CQI3UwqY.js → dynamic-tools-D2gYkSBO.js} +482 -91
- package/dist/effective-mcp-catalog-Bbd98Inw.js +114 -0
- package/dist/{event-projector-usage-D6tE7_qD.js → event-projector-usage-Xp4AlR2k.js} +20 -3
- package/dist/harness.js +19 -14
- package/dist/index.js +292 -328
- package/dist/media-understanding-provider.js +6 -1
- package/dist/{models-BNeMlHgg.js → models-BBMKU4jA.js} +2 -2
- package/dist/{notification-correlation-BGLixv5_.js → notification-correlation-CU6_AnOb.js} +3 -3
- package/dist/{plugin-app-cache-key-CvzvmBRz.js → plugin-app-cache-key-DztPafOA.js} +135 -42
- package/dist/{rate-limits-D0IJT-a6.js → rate-limits-CU-gVCCS.js} +1 -1
- package/dist/{request-BTccUTD3.js → request-qKh331yu.js} +3 -3
- package/dist/{run-attempt--858NXDl.js → run-attempt-CY9WlDLc.js} +1060 -402
- package/dist/{runtime-artifact-C2ITXdhM.js → runtime-artifact-CmtG_t1L.js} +90 -24
- package/dist/{sandbox-guard-D1Wky__S.js → sandbox-guard-NuhpXmPB.js} +4 -0
- package/dist/{session-binding-ZSMX1omP.js → session-binding-BXpo9ymA.js} +1006 -891
- package/dist/{session-catalog-DsKaezgR.js → session-catalog-DyOgR4WH.js} +289 -56
- package/dist/{session-cli-CYJTfRLG.js → session-cli-2PZaFLRh.js} +2 -2
- package/dist/{session-upstream-marker-D7XlI9us.js → session-upstream-marker-B6U7tLw5.js} +1 -1
- package/dist/{settled-turn-finalizer-DHltNgtF.js → settled-turn-finalizer-CXY2Z9dT.js} +19 -6
- package/dist/{shared-client-wmgNFNYp.js → shared-client-DTbvH-XV.js} +1993 -1720
- package/dist/{side-question-BfFZCsJ-.js → side-question-_ulBk1AL.js} +12 -10
- package/dist/{thread-lifecycle-cqrOlJkq.js → thread-lifecycle-DF53ogiA.js} +980 -346
- package/dist/{transcript-mirror-2p7kl6e4.js → transcript-mirror-DzsdNCf5.js} +80 -41
- package/dist/{upstream-session-fork-vPjwkT1i.js → upstream-session-fork-Dy4Xvuoj.js} +3 -3
- package/dist/{usage-DM_I3mPT.js → usage-CQ6GhD5-.js} +3 -3
- package/dist/{web-search-provider.runtime-Bx6HAc_k.js → web-search-provider.runtime-CCFhg2z5.js} +3 -3
- package/package.json +6 -8
- package/npm-shrinkwrap.json +0 -202
- package/dist/{auth-binding-C1czuHPA.js → auth-binding-B4FfbcCg.js} +1 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { c as reclaimCurrentCodexSessionGeneration, i as createCodexSessionGenerationSupersededError,
|
|
3
|
-
import { g as assertCodexThreadResumeResponse } from "./transcript-mirror-
|
|
4
|
-
import { t as listAllCodexAppServerModels } from "./models-
|
|
5
|
-
import { t as CODEX_CONTROL_METHODS } from "./capabilities-
|
|
6
|
-
import { s as summarizeCodexAccountUsage } from "./rate-limits-
|
|
7
|
-
import { a as formatComputerUseStatus, c as formatSkills, i as formatCodexStatus, l as formatThreads, n as formatAccount, o as formatList, r as formatCodexDisplayText, s as formatModels, t as buildHelp, u as readString$1 } from "./command-formatters-
|
|
8
|
-
import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-
|
|
9
|
-
import { a as setCodexConversationFastMode, c as steerCodexConversationTurn, d as CODEX_NATIVE_EXECUTION_AUTH_ERROR, f as canMutateCodexHost, i as readCodexConversationActiveTurn, l as stopCodexConversationTurn, n as parseCodexFastModeArg, o as setCodexConversationModel, r as parseCodexPermissionsModeArg, s as setCodexConversationPermissions, t as formatPermissionsMode } from "./conversation-control-
|
|
1
|
+
import { I as resolveCodexAppServerAuthProfileIdForAgent, W as isJsonObject } from "./shared-client-DTbvH-XV.js";
|
|
2
|
+
import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, j as isCodexFastServiceTier, n as assertCodexBindingMayBeReplaced, r as bindingStoreKey, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-BXpo9ymA.js";
|
|
3
|
+
import { g as assertCodexThreadResumeResponse } from "./transcript-mirror-DzsdNCf5.js";
|
|
4
|
+
import { t as listAllCodexAppServerModels } from "./models-BBMKU4jA.js";
|
|
5
|
+
import { t as CODEX_CONTROL_METHODS } from "./capabilities-BFDTVYr4.js";
|
|
6
|
+
import { s as summarizeCodexAccountUsage } from "./rate-limits-CU-gVCCS.js";
|
|
7
|
+
import { a as formatComputerUseStatus, c as formatSkills, i as formatCodexStatus, l as formatThreads, n as formatAccount, o as formatList, r as formatCodexDisplayText, s as formatModels, t as buildHelp, u as readString$1 } from "./command-formatters-C336eRVS.js";
|
|
8
|
+
import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-NuhpXmPB.js";
|
|
9
|
+
import { a as setCodexConversationFastMode, c as steerCodexConversationTurn, d as CODEX_NATIVE_EXECUTION_AUTH_ERROR, f as canMutateCodexHost, i as readCodexConversationActiveTurn, l as stopCodexConversationTurn, n as parseCodexFastModeArg, o as setCodexConversationModel, r as parseCodexPermissionsModeArg, s as setCodexConversationPermissions, t as formatPermissionsMode } from "./conversation-control-xsIeXbo4.js";
|
|
10
10
|
import { i as readCodexConversationBindingData, n as createCodexCliNodeConversationBindingData, o as resolveCodexDefaultWorkspaceDir, r as createCodexConversationBindingData } from "./conversation-binding-data-BHRpYbX3.js";
|
|
11
|
-
import { a as resolveCodexBindingAppServerConnection } from "./notification-correlation-
|
|
12
|
-
import { c as requestOptions, l as safeCodexControlRequest, o as codexControlRequest, p as formatCodexCliSessions, s as readCodexStatusProbes } from "./session-catalog-
|
|
13
|
-
import {
|
|
11
|
+
import { a as resolveCodexBindingAppServerConnection } from "./notification-correlation-CU6_AnOb.js";
|
|
12
|
+
import { c as requestOptions, l as safeCodexControlRequest, o as codexControlRequest, p as formatCodexCliSessions, s as readCodexStatusProbes } from "./session-catalog-DyOgR4WH.js";
|
|
13
|
+
import { n as installCodexComputerUse, r as readCodexComputerUseStatus } from "./computer-use-CYEc8U3w.js";
|
|
14
14
|
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
15
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
16
15
|
import crypto from "node:crypto";
|
|
17
16
|
import { ensureAuthProfileStore, findNormalizedProviderValue, resolveAgentDir, resolveAuthProfileEligibility, resolveAuthProfileOrder, resolveProfileUnusableUntilForDisplay, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
18
17
|
import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
|
19
18
|
import { normalizeOptionalString, normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
20
19
|
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
20
|
+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
21
21
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
22
22
|
import { MODEL_SELECTION_LOCKED_MESSAGE, isModelSelectionLocked } from "openclaw/plugin-sdk/model-session-runtime";
|
|
23
23
|
//#region extensions/codex/src/command-account.ts
|
|
@@ -350,210 +350,8 @@ function buildCodexCommandPickerPresentation(title, prompt, buttons) {
|
|
|
350
350
|
};
|
|
351
351
|
}
|
|
352
352
|
//#endregion
|
|
353
|
-
//#region extensions/codex/src/command-
|
|
354
|
-
|
|
355
|
-
async function handleCodexPluginsSubcommand(ctx, rest, io) {
|
|
356
|
-
const [verb = "list", ...args] = rest;
|
|
357
|
-
const normalized = verb.toLowerCase();
|
|
358
|
-
if (normalized === "menu") {
|
|
359
|
-
if (args.length > 0) return { text: "Usage: /codex plugins menu" };
|
|
360
|
-
return buildPluginsMenuReply();
|
|
361
|
-
}
|
|
362
|
-
if (normalized === "help") {
|
|
363
|
-
if (args.length > 0) return { text: "Usage: /codex plugins help" };
|
|
364
|
-
return { text: buildPluginsHelp() };
|
|
365
|
-
}
|
|
366
|
-
if (normalized === "list") {
|
|
367
|
-
if (args.length > 0) return { text: "Usage: /codex plugins list" };
|
|
368
|
-
const current = await io.readConfig();
|
|
369
|
-
return { text: formatPluginList(current.plugins ?? {}, { globalEnabled: current.enabled === true }) };
|
|
370
|
-
}
|
|
371
|
-
const target = args[0];
|
|
372
|
-
if (normalized === "enable" || normalized === "disable") {
|
|
373
|
-
if (args.length === 0) return buildPluginNamePickerReply(normalized, await io.readConfig());
|
|
374
|
-
if (!target || args.length > 1) return { text: `Usage: /codex plugins ${normalized} <name>` };
|
|
375
|
-
if (!canMutateCodexHost(ctx)) return { text: `Only an owner or operator.admin gateway client can run /codex plugins ${normalized}.` };
|
|
376
|
-
const wantEnabled = normalized === "enable";
|
|
377
|
-
if (!((await io.readConfig()).plugins ?? {})[target]) return { text: `Codex sub-plugin '${formatCodexDisplayText(target)}' is not configured. Run '/codex plugins list' to see configured plugins.` };
|
|
378
|
-
await io.mutate((block) => {
|
|
379
|
-
if (wantEnabled) block.enabled = true;
|
|
380
|
-
block.plugins ??= {};
|
|
381
|
-
block.plugins[target] = {
|
|
382
|
-
...block.plugins[target],
|
|
383
|
-
enabled: wantEnabled
|
|
384
|
-
};
|
|
385
|
-
});
|
|
386
|
-
return { text: `${formatCodexDisplayText(target)}: ${wantEnabled ? "enabled" : "disabled"} in openclaw.json. ${POLICY_REFRESH_HINT}` };
|
|
387
|
-
}
|
|
388
|
-
return { text: `Unknown /codex plugins subcommand: ${formatCodexDisplayText(verb)}\n\n${buildPluginsHelp()}` };
|
|
389
|
-
}
|
|
390
|
-
function buildPluginsMenuReply() {
|
|
391
|
-
return {
|
|
392
|
-
text: [
|
|
393
|
-
"Codex sub-plugins. Pick a sub-action or type:",
|
|
394
|
-
"",
|
|
395
|
-
" 1. /codex plugins list",
|
|
396
|
-
" 2. /codex plugins enable",
|
|
397
|
-
" 3. /codex plugins disable",
|
|
398
|
-
" 4. /codex plugins help",
|
|
399
|
-
"",
|
|
400
|
-
"Type '/codex' to go back to the main menu."
|
|
401
|
-
].join("\n"),
|
|
402
|
-
presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", "Pick a Codex sub-plugin action:", [
|
|
403
|
-
{
|
|
404
|
-
label: "list",
|
|
405
|
-
command: "/codex plugins list"
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
label: "enable",
|
|
409
|
-
command: "/codex plugins enable"
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
label: "disable",
|
|
413
|
-
command: "/codex plugins disable"
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
label: "help",
|
|
417
|
-
command: "/codex plugins help"
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
label: "back",
|
|
421
|
-
command: "/codex"
|
|
422
|
-
}
|
|
423
|
-
])
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
function buildPluginNamePickerReply(verb, current) {
|
|
427
|
-
const globalEnabled = current.enabled === true;
|
|
428
|
-
const eligible = Object.entries(current.plugins ?? {}).toSorted(([left], [right]) => left.localeCompare(right)).filter(([, entry]) => {
|
|
429
|
-
const effectivelyEnabled = globalEnabled && entry.enabled !== false;
|
|
430
|
-
return verb === "disable" ? effectivelyEnabled : !effectivelyEnabled;
|
|
431
|
-
});
|
|
432
|
-
if (eligible.length === 0) return {
|
|
433
|
-
text: [
|
|
434
|
-
`No configured ${verb === "enable" ? "disabled" : "enabled"} Codex sub-plugins found.`,
|
|
435
|
-
"",
|
|
436
|
-
"Type '/codex plugins list' to inspect configured sub-plugins.",
|
|
437
|
-
"Type '/codex plugins menu' to go back to the plugins menu."
|
|
438
|
-
].join("\n"),
|
|
439
|
-
presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", "Pick another Codex sub-plugin action:", [{
|
|
440
|
-
label: "list",
|
|
441
|
-
command: "/codex plugins list"
|
|
442
|
-
}, {
|
|
443
|
-
label: "back",
|
|
444
|
-
command: "/codex plugins menu"
|
|
445
|
-
}])
|
|
446
|
-
};
|
|
447
|
-
const buttons = [...eligible.map(([key]) => ({
|
|
448
|
-
label: formatCodexDisplayText(key),
|
|
449
|
-
command: `/codex plugins ${verb} ${key}`
|
|
450
|
-
})), {
|
|
451
|
-
label: "back",
|
|
452
|
-
command: "/codex plugins menu"
|
|
453
|
-
}];
|
|
454
|
-
return {
|
|
455
|
-
text: [
|
|
456
|
-
`Codex sub-plugins to ${verb}. Pick one or type:`,
|
|
457
|
-
"",
|
|
458
|
-
...eligible.map(([key], index) => ` ${index + 1}. /codex plugins ${verb} ${key}`),
|
|
459
|
-
"",
|
|
460
|
-
...verb === "enable" && !globalEnabled ? ["Global codexPlugins.enabled is off; enabling one configured sub-plugin turns it on.", ""] : [],
|
|
461
|
-
"Type '/codex plugins menu' to go back to the plugins menu."
|
|
462
|
-
].join("\n"),
|
|
463
|
-
presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", `Pick a Codex sub-plugin to ${verb}:`, buttons)
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
function buildPluginsHelp() {
|
|
467
|
-
return [
|
|
468
|
-
"Codex sub-plugin management (writes only to ~/.openclaw/openclaw.json, never to ~/.codex/config.toml):",
|
|
469
|
-
"- /codex plugins (alias for list)",
|
|
470
|
-
"- /codex plugins list show all configured Codex sub-plugins",
|
|
471
|
-
"- /codex plugins enable <name> enable a configured sub-plugin",
|
|
472
|
-
"- /codex plugins disable <name> disable a configured sub-plugin"
|
|
473
|
-
].join("\n");
|
|
474
|
-
}
|
|
475
|
-
function formatPluginList(plugins, options = {}) {
|
|
476
|
-
const globalEnabled = options.globalEnabled === true;
|
|
477
|
-
const keys = Object.keys(plugins).toSorted();
|
|
478
|
-
if (keys.length === 0) return "No Codex sub-plugins configured under plugins.entries.codex.config.codexPlugins.plugins";
|
|
479
|
-
const rows = keys.map((key) => {
|
|
480
|
-
const entry = plugins[key] ?? {};
|
|
481
|
-
const state = globalEnabled && entry.enabled !== false ? "ON " : "OFF";
|
|
482
|
-
return {
|
|
483
|
-
displayKey: formatCodexDisplayText(key),
|
|
484
|
-
state,
|
|
485
|
-
pluginName: formatCodexDisplayText(entry.pluginName ?? key),
|
|
486
|
-
marketplace: formatCodexDisplayText(entry.marketplaceName ?? "?")
|
|
487
|
-
};
|
|
488
|
-
});
|
|
489
|
-
const keyW = Math.max(...rows.map((r) => r.displayKey.length));
|
|
490
|
-
const pluginW = Math.max(...rows.map((r) => r.pluginName.length));
|
|
491
|
-
return [
|
|
492
|
-
"Codex sub-plugins in Openclaw config (~/.openclaw/openclaw.json):",
|
|
493
|
-
"",
|
|
494
|
-
...rows.map((r) => ` ${r.state} ${r.displayKey.padEnd(keyW)} ${r.pluginName.padEnd(pluginW)} [${r.marketplace}]`),
|
|
495
|
-
"",
|
|
496
|
-
...globalEnabled ? [] : ["Global codexPlugins.enabled is off; configured sub-plugins are inactive.", ""],
|
|
497
|
-
"New Codex conversations pick up policy changes automatically; /new or /reset to refresh the current one."
|
|
498
|
-
].join("\n");
|
|
499
|
-
}
|
|
500
|
-
//#endregion
|
|
501
|
-
//#region extensions/codex/src/command-handlers.ts
|
|
502
|
-
const defaultCodexCommandDeps = {
|
|
503
|
-
codexControlRequest,
|
|
504
|
-
listCodexAppServerModels: listAllCodexAppServerModels,
|
|
505
|
-
readCodexStatusProbes,
|
|
506
|
-
requestOptions,
|
|
507
|
-
safeCodexControlRequest,
|
|
508
|
-
readCodexComputerUseStatus,
|
|
509
|
-
installCodexComputerUse,
|
|
510
|
-
resolveCodexDefaultWorkspaceDir,
|
|
511
|
-
readCodexConversationActiveTurn,
|
|
512
|
-
setCodexConversationFastMode,
|
|
513
|
-
setCodexConversationModel,
|
|
514
|
-
setCodexConversationPermissions,
|
|
515
|
-
steerCodexConversationTurn,
|
|
516
|
-
stopCodexConversationTurn,
|
|
517
|
-
listCodexCliSessionsOnNode: async () => {
|
|
518
|
-
throw new Error("Codex CLI node sessions require Gateway node runtime.");
|
|
519
|
-
},
|
|
520
|
-
resolveCodexCliSessionForBindingOnNode: async () => {
|
|
521
|
-
throw new Error("Codex CLI node sessions require Gateway node runtime.");
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
const CODEX_DIAGNOSTICS_SOURCE = "openclaw-diagnostics";
|
|
525
|
-
const CODEX_DIAGNOSTICS_REASON_MAX_CHARS = 2048;
|
|
526
|
-
const CODEX_DIAGNOSTICS_COOLDOWN_MS = 6e4;
|
|
527
|
-
const CODEX_DIAGNOSTICS_ERROR_MAX_CHARS = 500;
|
|
528
|
-
const CODEX_DIAGNOSTICS_COOLDOWN_MAX_THREADS = 100;
|
|
529
|
-
const CODEX_DIAGNOSTICS_COOLDOWN_MAX_SCOPES = 100;
|
|
530
|
-
const CODEX_DIAGNOSTICS_CONFIRMATION_TTL_MS = 5 * 6e4;
|
|
531
|
-
const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_REQUESTS_PER_SCOPE = 100;
|
|
532
|
-
const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_SCOPES = 100;
|
|
533
|
-
const CODEX_DIAGNOSTICS_SCOPE_FIELD_MAX_CHARS = 128;
|
|
534
|
-
const CODEX_RESUME_SAFE_THREAD_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;
|
|
535
|
-
const CODEX_NATIVE_EXECUTION_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
536
|
-
"bind",
|
|
537
|
-
"resume",
|
|
538
|
-
"steer",
|
|
539
|
-
"model",
|
|
540
|
-
"fast",
|
|
541
|
-
"permissions",
|
|
542
|
-
"compact",
|
|
543
|
-
"review",
|
|
544
|
-
"goal"
|
|
545
|
-
]);
|
|
546
|
-
const CODEX_NATIVE_CONTROL_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
547
|
-
...CODEX_NATIVE_EXECUTION_SUBCOMMANDS,
|
|
548
|
-
"detach",
|
|
549
|
-
"unbind",
|
|
550
|
-
"stop"
|
|
551
|
-
]);
|
|
552
|
-
const { lastUploadByThread: lastCodexDiagnosticsUploadByThread, lastUploadByScope: lastCodexDiagnosticsUploadByScope, pendingConfirmations: pendingCodexDiagnosticsConfirmations, pendingTokensByScope: pendingCodexDiagnosticsConfirmationTokensByScope } = codexDiagnosticsFeedbackState;
|
|
553
|
-
/**
|
|
554
|
-
* No-arg `/codex` picker. Codex owns the command tree; channels render the
|
|
555
|
-
* portable command actions as inline controls when their transport can.
|
|
556
|
-
*/
|
|
353
|
+
//#region extensions/codex/src/command-handler-args.ts
|
|
354
|
+
/** No-arg `/codex` picker. */
|
|
557
355
|
function buildCodexSubcommandPickerReply() {
|
|
558
356
|
const verbs = [
|
|
559
357
|
{
|
|
@@ -596,7 +394,6 @@ function buildCodexSubcommandPickerReply() {
|
|
|
596
394
|
presentation: buildCodexCommandPickerPresentation("Codex commands", "Pick a Codex subcommand:", verbs)
|
|
597
395
|
};
|
|
598
396
|
}
|
|
599
|
-
/** Sub-picker for `/codex fast menu` (on / off / status). */
|
|
600
397
|
function buildCodexFastMenuReply() {
|
|
601
398
|
const modes = [
|
|
602
399
|
"on",
|
|
@@ -621,7 +418,6 @@ function buildCodexFastMenuReply() {
|
|
|
621
418
|
presentation: buildCodexCommandPickerPresentation("Codex fast mode", "Pick a Codex fast mode:", buttons)
|
|
622
419
|
};
|
|
623
420
|
}
|
|
624
|
-
/** Sub-picker for `/codex permissions menu` (default / yolo / status). */
|
|
625
421
|
function buildCodexPermissionsMenuReply() {
|
|
626
422
|
const modes = [
|
|
627
423
|
"default",
|
|
@@ -646,7 +442,6 @@ function buildCodexPermissionsMenuReply() {
|
|
|
646
442
|
presentation: buildCodexCommandPickerPresentation("Codex permissions", "Pick a Codex permissions mode:", buttons)
|
|
647
443
|
};
|
|
648
444
|
}
|
|
649
|
-
/** Sub-picker for `/codex computer-use menu` (status / install). */
|
|
650
445
|
function buildCodexComputerUseMenuReply() {
|
|
651
446
|
const actions = ["status", "install"];
|
|
652
447
|
const buttons = [...actions.map((action) => ({
|
|
@@ -669,503 +464,595 @@ function buildCodexComputerUseMenuReply() {
|
|
|
669
464
|
presentation: buildCodexCommandPickerPresentation("Codex computer-use", "Pick a Codex computer-use action:", buttons)
|
|
670
465
|
};
|
|
671
466
|
}
|
|
672
|
-
/** Returns true when the rest-args are exactly `["menu"]` (case-insensitive). */
|
|
673
467
|
function isMenuVerb(rest) {
|
|
674
468
|
return rest.length === 1 && (rest[0] ?? "").trim().toLowerCase() === "menu";
|
|
675
469
|
}
|
|
676
|
-
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
if (
|
|
691
|
-
|
|
470
|
+
function splitArgs(value) {
|
|
471
|
+
const input = value ?? "";
|
|
472
|
+
const args = [];
|
|
473
|
+
let current = "";
|
|
474
|
+
let quote;
|
|
475
|
+
let escaping = false;
|
|
476
|
+
let tokenStarted = false;
|
|
477
|
+
for (const char of input) {
|
|
478
|
+
if (escaping) {
|
|
479
|
+
current += char;
|
|
480
|
+
escaping = false;
|
|
481
|
+
tokenStarted = true;
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (char === "\\" && quote !== "'") {
|
|
485
|
+
escaping = true;
|
|
486
|
+
tokenStarted = true;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (quote) {
|
|
490
|
+
if (char === quote) quote = void 0;
|
|
491
|
+
else current += char;
|
|
492
|
+
tokenStarted = true;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (char === "\"" || char === "'") {
|
|
496
|
+
quote = char;
|
|
497
|
+
tokenStarted = true;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (/\s/.test(char)) {
|
|
501
|
+
if (tokenStarted) {
|
|
502
|
+
args.push(current);
|
|
503
|
+
current = "";
|
|
504
|
+
tokenStarted = false;
|
|
505
|
+
}
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
current += char;
|
|
509
|
+
tokenStarted = true;
|
|
692
510
|
}
|
|
693
|
-
if (
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
511
|
+
if (escaping) current += "\\";
|
|
512
|
+
if (tokenStarted) args.push(current);
|
|
513
|
+
return args;
|
|
514
|
+
}
|
|
515
|
+
function parseBindArgs(args) {
|
|
516
|
+
const parsed = {};
|
|
517
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
518
|
+
const arg = expectDefined(args[index], "current Codex bind argument");
|
|
519
|
+
if (arg === "--help" || arg === "-h") {
|
|
520
|
+
parsed.help = true;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (arg === "--cwd") {
|
|
524
|
+
const value = readRequiredOptionValue(args, index);
|
|
525
|
+
if (!value || parsed.cwd !== void 0) {
|
|
526
|
+
parsed.help = true;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
parsed.cwd = value;
|
|
530
|
+
index += 1;
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if (arg === "--model") {
|
|
534
|
+
const value = readRequiredOptionValue(args, index);
|
|
535
|
+
if (!value || parsed.model !== void 0) {
|
|
536
|
+
parsed.help = true;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
parsed.model = value;
|
|
540
|
+
index += 1;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (arg === "--provider" || arg === "--model-provider") {
|
|
544
|
+
const value = readRequiredOptionValue(args, index);
|
|
545
|
+
if (!value || parsed.provider !== void 0) {
|
|
546
|
+
parsed.help = true;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
parsed.provider = value;
|
|
550
|
+
index += 1;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (!arg.startsWith("-") && !parsed.threadId) {
|
|
554
|
+
parsed.threadId = arg;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
parsed.help = true;
|
|
725
558
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
559
|
+
parsed.threadId = normalizeOptionalString(parsed.threadId);
|
|
560
|
+
parsed.cwd = normalizeOptionalString(parsed.cwd);
|
|
561
|
+
parsed.model = normalizeOptionalString(parsed.model);
|
|
562
|
+
parsed.provider = normalizeOptionalString(parsed.provider);
|
|
563
|
+
return parsed;
|
|
564
|
+
}
|
|
565
|
+
function parseCodexCliSessionsArgs(args) {
|
|
566
|
+
const parsed = { filter: "" };
|
|
567
|
+
const filter = [];
|
|
568
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
569
|
+
const arg = expectDefined(args[index], "current Codex sessions argument");
|
|
570
|
+
if (arg === "--help" || arg === "-h") {
|
|
571
|
+
parsed.help = true;
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (arg === "--host" || arg === "--node") {
|
|
575
|
+
const value = readRequiredOptionValue(args, index);
|
|
576
|
+
if (!value || parsed.host !== void 0) {
|
|
577
|
+
parsed.help = true;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
parsed.host = value;
|
|
581
|
+
index += 1;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (arg === "--limit") {
|
|
585
|
+
const parsedLimit = parseStrictPositiveInteger(readRequiredOptionValue(args, index));
|
|
586
|
+
if (parsedLimit === void 0) {
|
|
587
|
+
parsed.help = true;
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
parsed.limit = parsedLimit;
|
|
591
|
+
index += 1;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (arg.startsWith("-")) {
|
|
595
|
+
parsed.help = true;
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
filter.push(arg);
|
|
729
599
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
600
|
+
parsed.host = normalizeOptionalString(parsed.host);
|
|
601
|
+
parsed.filter = filter.join(" ").trim();
|
|
602
|
+
return parsed;
|
|
603
|
+
}
|
|
604
|
+
function parseResumeArgs(args) {
|
|
605
|
+
const parsed = {};
|
|
606
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
607
|
+
const arg = expectDefined(args[index], "current Codex resume argument");
|
|
608
|
+
if (arg === "--help" || arg === "-h") {
|
|
609
|
+
parsed.help = true;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (arg === "--host" || arg === "--node") {
|
|
613
|
+
const value = readRequiredOptionValue(args, index);
|
|
614
|
+
if (!value || parsed.host !== void 0) {
|
|
615
|
+
parsed.help = true;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
parsed.host = value;
|
|
619
|
+
index += 1;
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
if (arg === "--bind") {
|
|
623
|
+
if (readRequiredOptionValue(args, index) !== "here" || parsed.bindHere !== void 0) {
|
|
624
|
+
parsed.help = true;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
parsed.bindHere = true;
|
|
628
|
+
index += 1;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (!arg.startsWith("-") && !parsed.threadId) {
|
|
632
|
+
parsed.threadId = arg;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
parsed.help = true;
|
|
736
636
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
637
|
+
parsed.threadId = normalizeOptionalString(parsed.threadId);
|
|
638
|
+
parsed.host = normalizeOptionalString(parsed.host);
|
|
639
|
+
return parsed;
|
|
640
|
+
}
|
|
641
|
+
function parseComputerUseArgs(args) {
|
|
642
|
+
const parsed = {
|
|
643
|
+
action: "status",
|
|
644
|
+
overrides: {},
|
|
645
|
+
hasOverrides: false,
|
|
646
|
+
persistentIdentity: {}
|
|
647
|
+
};
|
|
648
|
+
let sawAction = false;
|
|
649
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
650
|
+
const arg = args[index];
|
|
651
|
+
if (arg === "--help" || arg === "-h") {
|
|
652
|
+
parsed.help = true;
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (arg === "status" || arg === "install") {
|
|
656
|
+
if (sawAction) {
|
|
657
|
+
parsed.help = true;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
sawAction = true;
|
|
661
|
+
parsed.action = arg;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (arg === "--source" || arg === "--marketplace-source") {
|
|
665
|
+
const value = readRequiredOptionValue(args, index);
|
|
666
|
+
if (!value || parsed.overrides.marketplaceSource !== void 0) {
|
|
667
|
+
parsed.help = true;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
parsed.overrides.marketplaceSource = value;
|
|
671
|
+
index += 1;
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (arg === "--marketplace-path" || arg === "--path") {
|
|
675
|
+
const value = readRequiredOptionValue(args, index);
|
|
676
|
+
if (!value || parsed.overrides.marketplacePath !== void 0) {
|
|
677
|
+
parsed.help = true;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
parsed.overrides.marketplacePath = value;
|
|
681
|
+
index += 1;
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (arg === "--marketplace") {
|
|
685
|
+
const value = readRequiredOptionValue(args, index);
|
|
686
|
+
if (!value || parsed.overrides.marketplaceName !== void 0) {
|
|
687
|
+
parsed.help = true;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
parsed.overrides.marketplaceName = value;
|
|
691
|
+
index += 1;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (arg === "--plugin" || arg === "--server" || arg === "--mcp-server") {
|
|
695
|
+
const value = readRequiredOptionValue(args, index);
|
|
696
|
+
const configKey = arg === "--plugin" ? "pluginName" : "mcpServerName";
|
|
697
|
+
if (!value || parsed.persistentIdentity[configKey] !== void 0) {
|
|
698
|
+
parsed.help = true;
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
parsed.persistentIdentity[configKey] = value.trim();
|
|
702
|
+
index += 1;
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
parsed.help = true;
|
|
744
706
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
707
|
+
parsed.overrides = normalizeComputerUseStringOverrides(parsed.overrides);
|
|
708
|
+
parsed.hasOverrides = Object.values(parsed.overrides).some(Boolean);
|
|
709
|
+
return parsed;
|
|
710
|
+
}
|
|
711
|
+
function formatComputerUsePersistentIdentityMigration(parsed) {
|
|
712
|
+
const configPrefix = "plugins.entries.codex.config.computerUse";
|
|
713
|
+
const settings = [parsed.persistentIdentity.pluginName ? `${configPrefix}.pluginName = ${JSON.stringify(parsed.persistentIdentity.pluginName)}` : void 0, parsed.persistentIdentity.mcpServerName ? `${configPrefix}.mcpServerName = ${JSON.stringify(parsed.persistentIdentity.mcpServerName)}` : void 0].filter((setting) => Boolean(setting));
|
|
714
|
+
const retryArgs = [
|
|
715
|
+
`/codex computer-use ${parsed.action}`,
|
|
716
|
+
parsed.overrides.marketplaceSource ? `--source ${JSON.stringify(parsed.overrides.marketplaceSource)}` : void 0,
|
|
717
|
+
parsed.overrides.marketplacePath ? `--marketplace-path ${JSON.stringify(parsed.overrides.marketplacePath)}` : void 0,
|
|
718
|
+
parsed.overrides.marketplaceName ? `--marketplace ${JSON.stringify(parsed.overrides.marketplaceName)}` : void 0
|
|
719
|
+
].filter((arg) => Boolean(arg));
|
|
720
|
+
return ["One-off Computer Use plugin/server overrides are no longer supported.", `Set ${settings.join(" and ")} persistently, then rerun ${retryArgs.join(" ")}.`].join(" ");
|
|
721
|
+
}
|
|
722
|
+
function readRequiredOptionValue(args, index) {
|
|
723
|
+
const value = args[index + 1];
|
|
724
|
+
const normalized = value?.trim();
|
|
725
|
+
if (!normalized || normalized.startsWith("-")) return;
|
|
726
|
+
return value;
|
|
727
|
+
}
|
|
728
|
+
function normalizeComputerUseStringOverrides(overrides) {
|
|
729
|
+
const normalized = {};
|
|
730
|
+
const marketplaceSource = normalizeOptionalString(overrides.marketplaceSource);
|
|
731
|
+
if (marketplaceSource) normalized.marketplaceSource = marketplaceSource;
|
|
732
|
+
const marketplacePath = normalizeOptionalString(overrides.marketplacePath);
|
|
733
|
+
if (marketplacePath) normalized.marketplacePath = marketplacePath;
|
|
734
|
+
const marketplaceName = normalizeOptionalString(overrides.marketplaceName);
|
|
735
|
+
if (marketplaceName) normalized.marketplaceName = marketplaceName;
|
|
736
|
+
return normalized;
|
|
737
|
+
}
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region extensions/codex/src/command-diagnostics-support.ts
|
|
740
|
+
const CODEX_DIAGNOSTICS_SOURCE = "openclaw-diagnostics";
|
|
741
|
+
const CODEX_DIAGNOSTICS_REASON_MAX_CHARS = 2048;
|
|
742
|
+
const CODEX_DIAGNOSTICS_COOLDOWN_MS = 6e4;
|
|
743
|
+
const CODEX_DIAGNOSTICS_ERROR_MAX_CHARS = 500;
|
|
744
|
+
const CODEX_DIAGNOSTICS_COOLDOWN_MAX_THREADS = 100;
|
|
745
|
+
const CODEX_DIAGNOSTICS_COOLDOWN_MAX_SCOPES = 100;
|
|
746
|
+
const CODEX_DIAGNOSTICS_CONFIRMATION_TTL_MS = 5 * 6e4;
|
|
747
|
+
const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_REQUESTS_PER_SCOPE = 100;
|
|
748
|
+
const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_SCOPES = 100;
|
|
749
|
+
const CODEX_DIAGNOSTICS_SCOPE_FIELD_MAX_CHARS = 128;
|
|
750
|
+
const CODEX_RESUME_SAFE_THREAD_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;
|
|
751
|
+
const { lastUploadByThread: lastCodexDiagnosticsUploadByThread, lastUploadByScope: lastCodexDiagnosticsUploadByScope, pendingConfirmations: pendingCodexDiagnosticsConfirmations, pendingTokensByScope: pendingCodexDiagnosticsConfirmationTokensByScope } = codexDiagnosticsFeedbackState;
|
|
752
|
+
function normalizeDiagnosticsReason(note) {
|
|
753
|
+
const normalized = normalizeOptionalString(note);
|
|
754
|
+
return normalized ? truncateUtf16Safe(normalized, CODEX_DIAGNOSTICS_REASON_MAX_CHARS) : void 0;
|
|
755
|
+
}
|
|
756
|
+
function parseDiagnosticsArgs(args) {
|
|
757
|
+
const [action, token, ...extra] = splitArgs(args);
|
|
758
|
+
const normalizedAction = action?.toLowerCase();
|
|
759
|
+
if ((normalizedAction === "confirm" || normalizedAction === "--confirm") && token && extra.length === 0) return {
|
|
760
|
+
action: "confirm",
|
|
761
|
+
token
|
|
762
|
+
};
|
|
763
|
+
if ((normalizedAction === "cancel" || normalizedAction === "--cancel") && token && extra.length === 0) return {
|
|
764
|
+
action: "cancel",
|
|
765
|
+
token
|
|
766
|
+
};
|
|
767
|
+
if (normalizedAction === "confirm" || normalizedAction === "--confirm" || normalizedAction === "cancel" || normalizedAction === "--cancel") return { action: "usage" };
|
|
768
|
+
return {
|
|
769
|
+
action: "request",
|
|
770
|
+
note: args
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function formatDiagnosticsUsage(commandPrefix) {
|
|
774
|
+
return [
|
|
775
|
+
`Usage: ${commandPrefix} [note]`,
|
|
776
|
+
`Usage: ${commandPrefix} confirm <token>`,
|
|
777
|
+
`Usage: ${commandPrefix} cancel <token>`
|
|
778
|
+
].join("\n");
|
|
779
|
+
}
|
|
780
|
+
function createCodexDiagnosticsConfirmation(params) {
|
|
781
|
+
prunePendingCodexDiagnosticsConfirmations(params.now);
|
|
782
|
+
if (!pendingCodexDiagnosticsConfirmationTokensByScope.has(params.scopeKey) && pendingCodexDiagnosticsConfirmationTokensByScope.size >= CODEX_DIAGNOSTICS_CONFIRMATION_MAX_SCOPES) {
|
|
783
|
+
const oldestScopeKey = pendingCodexDiagnosticsConfirmationTokensByScope.keys().next().value;
|
|
784
|
+
if (typeof oldestScopeKey === "string") deletePendingCodexDiagnosticsConfirmationScope(oldestScopeKey);
|
|
752
785
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
...scope
|
|
759
|
-
};
|
|
760
|
-
const [account, limits] = await Promise.all([deps.safeCodexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, requestScope), deps.safeCodexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, requestScope)]);
|
|
761
|
-
return { text: formatAccount(account, limits, await readCodexAccountAuthOverview({
|
|
762
|
-
ctx,
|
|
763
|
-
agentDir: scope.agentDir,
|
|
764
|
-
pluginConfig: options.pluginConfig,
|
|
765
|
-
safeCodexControlRequest: deps.safeCodexControlRequest,
|
|
766
|
-
account,
|
|
767
|
-
limits
|
|
768
|
-
})) };
|
|
786
|
+
const scopeTokens = pendingCodexDiagnosticsConfirmationTokensByScope.get(params.scopeKey) ?? [];
|
|
787
|
+
while (scopeTokens.length >= CODEX_DIAGNOSTICS_CONFIRMATION_MAX_REQUESTS_PER_SCOPE) {
|
|
788
|
+
const oldestToken = scopeTokens.shift();
|
|
789
|
+
if (!oldestToken) break;
|
|
790
|
+
pendingCodexDiagnosticsConfirmations.delete(oldestToken);
|
|
769
791
|
}
|
|
770
|
-
|
|
792
|
+
const token = crypto.randomBytes(6).toString("hex");
|
|
793
|
+
scopeTokens.push(token);
|
|
794
|
+
pendingCodexDiagnosticsConfirmationTokensByScope.set(params.scopeKey, scopeTokens);
|
|
795
|
+
pendingCodexDiagnosticsConfirmations.set(token, {
|
|
796
|
+
token,
|
|
797
|
+
targets: params.targets,
|
|
798
|
+
note: params.note,
|
|
799
|
+
senderId: params.senderId,
|
|
800
|
+
channel: params.channel,
|
|
801
|
+
accountId: params.accountId,
|
|
802
|
+
channelId: params.channelId,
|
|
803
|
+
messageThreadId: params.messageThreadId,
|
|
804
|
+
threadParentId: params.threadParentId,
|
|
805
|
+
sessionKey: params.sessionKey,
|
|
806
|
+
scopeKey: params.scopeKey,
|
|
807
|
+
...params.privateRouted === void 0 ? {} : { privateRouted: params.privateRouted },
|
|
808
|
+
createdAt: params.now
|
|
809
|
+
});
|
|
810
|
+
return token;
|
|
771
811
|
}
|
|
772
|
-
function
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}));
|
|
812
|
+
function readCodexDiagnosticsConfirmationScope(ctx) {
|
|
813
|
+
return {
|
|
814
|
+
accountId: normalizeCodexDiagnosticsScopeField(ctx.accountId),
|
|
815
|
+
channelId: normalizeCodexDiagnosticsScopeField(ctx.channelId),
|
|
816
|
+
messageThreadId: typeof ctx.messageThreadId === "string" || typeof ctx.messageThreadId === "number" ? normalizeCodexDiagnosticsScopeField(String(ctx.messageThreadId)) : void 0,
|
|
817
|
+
threadParentId: normalizeCodexDiagnosticsScopeField(ctx.threadParentId),
|
|
818
|
+
sessionKey: normalizeCodexDiagnosticsScopeField(ctx.sessionKey)
|
|
819
|
+
};
|
|
781
820
|
}
|
|
782
|
-
function
|
|
783
|
-
|
|
784
|
-
if (
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
821
|
+
function readCodexDiagnosticsScopeMismatch(pending, ctx) {
|
|
822
|
+
const current = readCodexDiagnosticsConfirmationScope(ctx);
|
|
823
|
+
if (pending.accountId !== current.accountId) return {
|
|
824
|
+
confirmMessage: "This Codex diagnostics confirmation belongs to a different account.",
|
|
825
|
+
cancelMessage: "This Codex diagnostics confirmation belongs to a different account."
|
|
826
|
+
};
|
|
827
|
+
if (pending.privateRouted) return;
|
|
828
|
+
if (pending.channelId !== current.channelId) return {
|
|
829
|
+
confirmMessage: "This Codex diagnostics confirmation belongs to a different channel instance.",
|
|
830
|
+
cancelMessage: "This Codex diagnostics confirmation belongs to a different channel instance."
|
|
831
|
+
};
|
|
832
|
+
if (pending.messageThreadId !== current.messageThreadId) return {
|
|
833
|
+
confirmMessage: "This Codex diagnostics confirmation belongs to a different thread.",
|
|
834
|
+
cancelMessage: "This Codex diagnostics confirmation belongs to a different thread."
|
|
835
|
+
};
|
|
836
|
+
if (pending.threadParentId !== current.threadParentId) return {
|
|
837
|
+
confirmMessage: "This Codex diagnostics confirmation belongs to a different parent thread.",
|
|
838
|
+
cancelMessage: "This Codex diagnostics confirmation belongs to a different parent thread."
|
|
839
|
+
};
|
|
840
|
+
if (pending.sessionKey !== current.sessionKey) return {
|
|
841
|
+
confirmMessage: "This Codex diagnostics confirmation belongs to a different session.",
|
|
842
|
+
cancelMessage: "This Codex diagnostics confirmation belongs to a different session."
|
|
843
|
+
};
|
|
799
844
|
}
|
|
800
|
-
function
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
return action === "status" || action === "get";
|
|
845
|
+
function readPendingCodexDiagnosticsConfirmation(token, now) {
|
|
846
|
+
prunePendingCodexDiagnosticsConfirmations(now);
|
|
847
|
+
return pendingCodexDiagnosticsConfirmations.get(token);
|
|
804
848
|
}
|
|
805
|
-
function
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
849
|
+
function deletePendingCodexDiagnosticsConfirmation(token) {
|
|
850
|
+
const pending = pendingCodexDiagnosticsConfirmations.get(token);
|
|
851
|
+
pendingCodexDiagnosticsConfirmations.delete(token);
|
|
852
|
+
if (!pending) return;
|
|
853
|
+
const scopeTokens = pendingCodexDiagnosticsConfirmationTokensByScope.get(pending.scopeKey);
|
|
854
|
+
if (!scopeTokens) return;
|
|
855
|
+
const tokenIndex = scopeTokens.indexOf(token);
|
|
856
|
+
if (tokenIndex >= 0) scopeTokens.splice(tokenIndex, 1);
|
|
857
|
+
if (scopeTokens.length === 0) pendingCodexDiagnosticsConfirmationTokensByScope.delete(pending.scopeKey);
|
|
858
|
+
}
|
|
859
|
+
function prunePendingCodexDiagnosticsConfirmations(now) {
|
|
860
|
+
for (const [token, pending] of pendingCodexDiagnosticsConfirmations) if (now - pending.createdAt >= CODEX_DIAGNOSTICS_CONFIRMATION_TTL_MS) deletePendingCodexDiagnosticsConfirmation(token);
|
|
861
|
+
}
|
|
862
|
+
function deletePendingCodexDiagnosticsConfirmationScope(scopeKey) {
|
|
863
|
+
const scopeTokens = pendingCodexDiagnosticsConfirmationTokensByScope.get(scopeKey) ?? [];
|
|
864
|
+
for (const token of scopeTokens) pendingCodexDiagnosticsConfirmations.delete(token);
|
|
865
|
+
pendingCodexDiagnosticsConfirmationTokensByScope.delete(scopeKey);
|
|
866
|
+
}
|
|
867
|
+
function codexDiagnosticsTargetsMatch(expected, actual) {
|
|
868
|
+
const fingerprint = (target) => JSON.stringify([
|
|
869
|
+
bindingStoreKey(target.identity),
|
|
870
|
+
target.threadId,
|
|
871
|
+
target.connectionScope ?? null,
|
|
872
|
+
target.pendingSupervisionBranch?.connectionFingerprint ?? target.appServerRuntimeFingerprint ?? null,
|
|
873
|
+
target.authProfileId ?? null
|
|
874
|
+
]);
|
|
875
|
+
const expectedTargets = expected.map(fingerprint).toSorted();
|
|
876
|
+
const actualTargets = actual.map(fingerprint).toSorted();
|
|
877
|
+
return expectedTargets.length === actualTargets.length && expectedTargets.every((target, index) => target === actualTargets[index]);
|
|
878
|
+
}
|
|
879
|
+
function formatCodexDiagnosticsUploadResult(sent, failed) {
|
|
880
|
+
const lines = [];
|
|
881
|
+
if (sent.length > 0) {
|
|
882
|
+
lines.push("Codex diagnostics sent to OpenAI servers:");
|
|
883
|
+
lines.push(...formatCodexDiagnosticsTargetLines(sent));
|
|
884
|
+
lines.push("Included Codex logs and spawned Codex subthreads when available.");
|
|
885
|
+
}
|
|
886
|
+
if (failed.length > 0) {
|
|
887
|
+
if (lines.length > 0) lines.push("");
|
|
888
|
+
lines.push("Could not send Codex diagnostics:");
|
|
889
|
+
lines.push(...failed.map(({ target, error }) => `${formatCodexDiagnosticsTargetLine(target)}: ${formatCodexErrorForDisplay(error)}`));
|
|
890
|
+
lines.push("Inspect locally:");
|
|
891
|
+
lines.push(...failed.map(({ target }) => `- ${formatCodexResumeCommandForDisplay(target.threadId)}`));
|
|
819
892
|
}
|
|
893
|
+
return lines.join("\n");
|
|
820
894
|
}
|
|
821
|
-
function
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
895
|
+
function formatCodexDiagnosticsTargetLines(targets) {
|
|
896
|
+
return targets.flatMap((target, index) => {
|
|
897
|
+
const lines = formatCodexDiagnosticsTargetBlock(target, index);
|
|
898
|
+
return index < targets.length - 1 ? [...lines, ""] : lines;
|
|
899
|
+
});
|
|
825
900
|
}
|
|
826
|
-
function
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
if (
|
|
830
|
-
if (
|
|
831
|
-
|
|
901
|
+
function formatCodexDiagnosticsTargetBlock(target, index) {
|
|
902
|
+
const lines = [`Session ${index + 1}`];
|
|
903
|
+
if (target.channel) lines.push(`Channel: ${formatCodexValueForDisplay(target.channel)}`);
|
|
904
|
+
if (target.sessionKey) lines.push(`OpenClaw session key: ${formatCodexCopyableValueForDisplay(target.sessionKey)}`);
|
|
905
|
+
if (target.sessionId) lines.push(`OpenClaw session id: ${formatCodexCopyableValueForDisplay(target.sessionId)}`);
|
|
906
|
+
lines.push(`Codex thread id: ${formatCodexCopyableValueForDisplay(target.threadId)}`);
|
|
907
|
+
lines.push(`Inspect locally: ${formatCodexResumeCommandForDisplay(target.threadId)}`);
|
|
908
|
+
return lines;
|
|
832
909
|
}
|
|
833
|
-
|
|
834
|
-
const
|
|
835
|
-
if (
|
|
836
|
-
|
|
837
|
-
if (
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
pluginConfig,
|
|
841
|
-
config: ctx.config,
|
|
842
|
-
agentDir,
|
|
843
|
-
forceEnable: parsed.action === "install" || parsed.hasOverrides,
|
|
844
|
-
...Object.keys(parsed.overrides).length > 0 ? { overrides: parsed.overrides } : {}
|
|
845
|
-
};
|
|
846
|
-
if (parsed.action === "install") return formatComputerUseStatus(await deps.installCodexComputerUse(params));
|
|
847
|
-
return formatComputerUseStatus(await deps.readCodexComputerUseStatus(params));
|
|
910
|
+
function formatCodexDiagnosticsTargetLine(target) {
|
|
911
|
+
const parts = [];
|
|
912
|
+
if (target.channel) parts.push(`channel ${formatCodexValueForDisplay(target.channel)}`);
|
|
913
|
+
const sessionLabel = target.sessionId || target.sessionKey;
|
|
914
|
+
if (sessionLabel) parts.push(`OpenClaw session ${formatCodexValueForDisplay(sessionLabel)}`);
|
|
915
|
+
parts.push(`Codex thread ${formatCodexThreadIdForDisplay(target.threadId)}`);
|
|
916
|
+
return `- ${parts.join(", ")}`;
|
|
848
917
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
const sessionSource = sessionOwner && existingBinding ? {
|
|
868
|
-
agentId: sessionOwner.agentId,
|
|
869
|
-
sessionId: sessionOwner.sessionId,
|
|
870
|
-
threadId: existingBinding.threadId,
|
|
871
|
-
...sessionOwner.sessionKey ? { sessionKey: sessionOwner.sessionKey } : {}
|
|
872
|
-
} : void 0;
|
|
873
|
-
const authProfileId = existingBinding?.authProfileId;
|
|
874
|
-
const data = createCodexConversationBindingData({
|
|
875
|
-
bindingId,
|
|
876
|
-
workspaceDir,
|
|
877
|
-
agentId: scope.agentId,
|
|
878
|
-
agentDir: scope.agentDir,
|
|
879
|
-
source: currentConversationData?.kind === "codex-app-server-session" ? currentConversationData.source : sessionSource,
|
|
880
|
-
start: {
|
|
881
|
-
id: crypto.randomUUID(),
|
|
882
|
-
threadId: parsed.threadId,
|
|
883
|
-
model: parsed.model,
|
|
884
|
-
modelProvider: parsed.provider,
|
|
885
|
-
authProfileId
|
|
918
|
+
function escapeCodexChatText(value) {
|
|
919
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("@", "@").replaceAll("`", "`").replaceAll("[", "[").replaceAll("]", "]").replaceAll("(", "(").replaceAll(")", ")").replaceAll("*", "∗").replaceAll("_", "_").replaceAll("~", "~").replaceAll("|", "|");
|
|
920
|
+
}
|
|
921
|
+
function formatCodexTextForDisplay(value) {
|
|
922
|
+
let safe = "";
|
|
923
|
+
for (const character of value) {
|
|
924
|
+
const codePoint = character.codePointAt(0);
|
|
925
|
+
safe += codePoint != null && isUnsafeDisplayCodePoint(codePoint) ? "?" : character;
|
|
926
|
+
}
|
|
927
|
+
safe = safe.trim();
|
|
928
|
+
return safe || "<unknown>";
|
|
929
|
+
}
|
|
930
|
+
function readCodexDiagnosticsTargetsCooldownMessage(targets, ctx, now, options = {}) {
|
|
931
|
+
for (const target of targets) {
|
|
932
|
+
const cooldownMs = readCodexDiagnosticsCooldownMs(target.threadId, now);
|
|
933
|
+
if (cooldownMs > 0) {
|
|
934
|
+
if (options.includeThreadId === false) return `Codex diagnostics were already sent for one of these Codex threads recently. Try again in ${Math.ceil(cooldownMs / 1e3)}s.`;
|
|
935
|
+
return `Codex diagnostics were already sent for thread ${formatCodexThreadIdForDisplay(target.threadId)} recently. Try again in ${Math.ceil(cooldownMs / 1e3)}s.`;
|
|
886
936
|
}
|
|
887
|
-
}
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
937
|
+
}
|
|
938
|
+
const scopeCooldownMs = readCodexDiagnosticsScopeCooldownMs(options.cooldownScope ?? readCodexDiagnosticsCooldownScope(ctx), now);
|
|
939
|
+
if (scopeCooldownMs > 0) return `Codex diagnostics were already sent for this account or channel recently. Try again in ${Math.ceil(scopeCooldownMs / 1e3)}s.`;
|
|
940
|
+
}
|
|
941
|
+
function recordCodexDiagnosticsUpload(threadId, ctx, now, cooldownScope) {
|
|
942
|
+
pruneCodexDiagnosticsCooldowns(now);
|
|
943
|
+
recordBoundedCodexDiagnosticsCooldown(lastCodexDiagnosticsUploadByScope, cooldownScope ?? readCodexDiagnosticsCooldownScope(ctx), CODEX_DIAGNOSTICS_COOLDOWN_MAX_SCOPES, now);
|
|
944
|
+
recordBoundedCodexDiagnosticsCooldown(lastCodexDiagnosticsUploadByThread, threadId, CODEX_DIAGNOSTICS_COOLDOWN_MAX_THREADS, now);
|
|
945
|
+
}
|
|
946
|
+
function readCodexDiagnosticsCooldownScope(ctx) {
|
|
947
|
+
const scope = readCodexDiagnosticsConfirmationScope(ctx);
|
|
948
|
+
const payload = JSON.stringify({
|
|
949
|
+
accountId: scope.accountId ?? null,
|
|
950
|
+
channelId: scope.channelId ?? null,
|
|
951
|
+
sessionKey: scope.sessionKey ?? null,
|
|
952
|
+
messageThreadId: scope.messageThreadId ?? null,
|
|
953
|
+
threadParentId: scope.threadParentId ?? null,
|
|
954
|
+
senderId: normalizeCodexDiagnosticsScopeField(ctx.senderId) ?? null,
|
|
955
|
+
channel: normalizeCodexDiagnosticsScopeField(ctx.channel) ?? ""
|
|
956
|
+
});
|
|
957
|
+
return crypto.createHash("sha256").update(payload).digest("hex");
|
|
897
958
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const detached = await ctx.detachConversationBinding();
|
|
903
|
-
if (data?.kind === "codex-app-server-session") await deps.bindingStore.mutate(conversationBindingIdentity(data.bindingId), { kind: "clear" });
|
|
904
|
-
return detached.removed ? "Detached this conversation from Codex." : "No Codex conversation binding was attached.";
|
|
959
|
+
function buildDiagnosticsTags(ctx) {
|
|
960
|
+
const tags = { source: CODEX_DIAGNOSTICS_SOURCE };
|
|
961
|
+
addTag(tags, "channel", ctx.channel);
|
|
962
|
+
return tags;
|
|
905
963
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const data = readCodexConversationBindingData(current);
|
|
909
|
-
if (!current || !data) return "No Codex conversation binding is attached.";
|
|
910
|
-
if (data.kind === "codex-cli-node-session") return [
|
|
911
|
-
"Codex conversation binding:",
|
|
912
|
-
"- Mode: Codex CLI node session",
|
|
913
|
-
`- Node: ${formatCodexDisplayText(data.nodeId)}`,
|
|
914
|
-
`- Session: ${formatCodexDisplayText(data.sessionId)}`,
|
|
915
|
-
`- Workspace: ${formatCodexDisplayText(data.cwd ?? "unknown")}`,
|
|
916
|
-
"- Active run: not tracked"
|
|
917
|
-
].join("\n");
|
|
918
|
-
const identity = conversationBindingIdentity(data.bindingId);
|
|
919
|
-
const threadBinding = await deps.bindingStore.read(identity);
|
|
920
|
-
const active = deps.readCodexConversationActiveTurn(identity);
|
|
921
|
-
return [
|
|
922
|
-
"Codex conversation binding:",
|
|
923
|
-
`- Thread: ${formatCodexDisplayText(threadBinding?.threadId ?? "unknown")}`,
|
|
924
|
-
`- Workspace: ${formatCodexDisplayText(data.workspaceDir)}`,
|
|
925
|
-
`- Model: ${formatCodexDisplayText(threadBinding?.model ?? "default")}`,
|
|
926
|
-
`- Fast: ${isCodexFastServiceTier(threadBinding?.serviceTier) ? "on" : "off"}`,
|
|
927
|
-
`- Permissions: ${threadBinding ? formatPermissionsMode(threadBinding) : "default"}`,
|
|
928
|
-
`- Active run: ${formatCodexDisplayText(active ? active.turnId : "none")}`,
|
|
929
|
-
`- Binding: ${formatCodexDisplayText(data.bindingId)}`
|
|
930
|
-
].join("\n");
|
|
964
|
+
function addTag(tags, key, value) {
|
|
965
|
+
if (typeof value === "string" && value.trim()) tags[key] = value.trim();
|
|
931
966
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
return formatThreads(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listThreads, {
|
|
935
|
-
limit: 10,
|
|
936
|
-
...filter.trim() ? { searchTerm: filter.trim() } : {}
|
|
937
|
-
}, {
|
|
938
|
-
config: ctx.config,
|
|
939
|
-
...scope
|
|
940
|
-
}));
|
|
967
|
+
function formatCodexThreadIdForDisplay(threadId) {
|
|
968
|
+
return escapeCodexChatText(formatCodexTextForDisplay(threadId));
|
|
941
969
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
const objective = args.slice(1).join(" ").trim();
|
|
945
|
-
const target = await resolveControlTarget(ctx);
|
|
946
|
-
if (!target) return "Cannot manage the Codex goal because this command has no stable binding identity.";
|
|
947
|
-
const binding = await deps.bindingStore.read(target.identity);
|
|
948
|
-
if (!binding?.threadId) return "No Codex thread is attached to this OpenClaw session yet.";
|
|
949
|
-
const connection = resolveCodexBindingAppServerConnection({
|
|
950
|
-
binding,
|
|
951
|
-
authProfileId: binding.authProfileId,
|
|
952
|
-
pluginConfig
|
|
953
|
-
});
|
|
954
|
-
const goalRequestOptions = {
|
|
955
|
-
agentDir: target.agentDir,
|
|
956
|
-
authProfileId: connection.clientAuthProfileId,
|
|
957
|
-
config: ctx.config,
|
|
958
|
-
...connection.usesSupervisionConnection ? { startOptions: connection.appServer.start } : {}
|
|
959
|
-
};
|
|
960
|
-
if (action === "status" || action === "get") {
|
|
961
|
-
if (args.length > 1) return "Usage: /codex goal [status]";
|
|
962
|
-
return formatNativeGoal(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.getThreadGoal, { threadId: binding.threadId }, goalRequestOptions));
|
|
963
|
-
}
|
|
964
|
-
if (action === "clear") {
|
|
965
|
-
if (args.length > 1) return "Usage: /codex goal clear";
|
|
966
|
-
const response = await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.clearThreadGoal, { threadId: binding.threadId }, goalRequestOptions);
|
|
967
|
-
return isJsonObject(response) && response.cleared === true ? "Cleared the Codex goal." : "No Codex goal was active.";
|
|
968
|
-
}
|
|
969
|
-
const requestedStatus = action === "pause" ? "paused" : action === "resume" ? "active" : action === "block" ? "blocked" : action === "complete" ? "complete" : void 0;
|
|
970
|
-
const isObjectiveUpdate = action === "set";
|
|
971
|
-
if (!requestedStatus && !isObjectiveUpdate || isObjectiveUpdate && !objective) return "Usage: /codex goal [status|set <objective>|pause|resume|block|complete|clear]";
|
|
972
|
-
if (requestedStatus && args.length > 1) return `Usage: /codex goal ${action}`;
|
|
973
|
-
return formatNativeGoal(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.setThreadGoal, {
|
|
974
|
-
threadId: binding.threadId,
|
|
975
|
-
...objective ? { objective } : {},
|
|
976
|
-
...requestedStatus ? { status: requestedStatus } : {}
|
|
977
|
-
}, goalRequestOptions));
|
|
970
|
+
function formatCodexValueForDisplay(value) {
|
|
971
|
+
return escapeCodexChatText(formatCodexTextForDisplay(value));
|
|
978
972
|
}
|
|
979
|
-
function
|
|
980
|
-
const
|
|
981
|
-
if (
|
|
982
|
-
|
|
983
|
-
const status = readString$1(goal, "status") ?? "unknown";
|
|
984
|
-
const tokensUsed = typeof goal.tokensUsed === "number" ? goal.tokensUsed : 0;
|
|
985
|
-
const tokenBudget = typeof goal.tokenBudget === "number" ? goal.tokenBudget : void 0;
|
|
986
|
-
return [
|
|
987
|
-
`Codex goal: ${formatCodexDisplayText(objective)}`,
|
|
988
|
-
`- Status: ${formatCodexDisplayText(status)}`,
|
|
989
|
-
`- Tokens: ${tokensUsed}${tokenBudget === void 0 ? "" : ` / ${tokenBudget}`}`
|
|
990
|
-
].join("\n");
|
|
973
|
+
function formatCodexCopyableValueForDisplay(value) {
|
|
974
|
+
const safe = formatCodexTextForDisplay(value);
|
|
975
|
+
if (CODEX_RESUME_SAFE_THREAD_ID_PATTERN.test(safe)) return `\`${safe}\``;
|
|
976
|
+
return escapeCodexChatText(safe);
|
|
991
977
|
}
|
|
992
|
-
|
|
993
|
-
const
|
|
994
|
-
if (
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
limit: parsed.limit
|
|
999
|
-
}));
|
|
978
|
+
function readCodexDiagnosticsCooldownMs(threadId, now) {
|
|
979
|
+
const lastSentAt = lastCodexDiagnosticsUploadByThread.get(threadId);
|
|
980
|
+
if (!lastSentAt) return 0;
|
|
981
|
+
const remainingMs = Math.max(0, CODEX_DIAGNOSTICS_COOLDOWN_MS - (now - lastSentAt));
|
|
982
|
+
if (remainingMs === 0) lastCodexDiagnosticsUploadByThread.delete(threadId);
|
|
983
|
+
return remainingMs;
|
|
1000
984
|
}
|
|
1001
|
-
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1007
|
-
if (isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
|
|
1008
|
-
if (!ctx.sessionId) return "Cannot attach a Codex thread because this command did not include an OpenClaw session id.";
|
|
1009
|
-
const scope = resolveCodexConversationControlScope(ctx);
|
|
1010
|
-
const identity = sessionBindingIdentity({
|
|
1011
|
-
sessionId: ctx.sessionId,
|
|
1012
|
-
sessionKey: ctx.sessionKey,
|
|
1013
|
-
agentId: scope.agentId,
|
|
1014
|
-
config: ctx.config
|
|
1015
|
-
});
|
|
1016
|
-
return await deps.bindingStore.withLease(identity, async () => {
|
|
1017
|
-
if (!await reclaimCurrentCodexSessionGeneration({
|
|
1018
|
-
bindingStore: deps.bindingStore,
|
|
1019
|
-
identity,
|
|
1020
|
-
config: ctx.config
|
|
1021
|
-
})) throw createCodexSessionGenerationSupersededError(identity.sessionId);
|
|
1022
|
-
const currentBinding = await deps.bindingStore.read(identity);
|
|
1023
|
-
assertCodexBindingMayBeReplaced(currentBinding, "attaching a different resumed thread");
|
|
1024
|
-
const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
|
|
1025
|
-
authProfileId: currentBinding?.authProfileId,
|
|
1026
|
-
agentDir: scope.agentDir,
|
|
1027
|
-
config: ctx.config
|
|
1028
|
-
});
|
|
1029
|
-
const response = assertCodexThreadResumeResponse(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.resumeThread, {
|
|
1030
|
-
threadId: normalizedThreadId,
|
|
1031
|
-
excludeTurns: true
|
|
1032
|
-
}, {
|
|
1033
|
-
config: ctx.config,
|
|
1034
|
-
agentDir: scope.agentDir,
|
|
1035
|
-
authProfileId,
|
|
1036
|
-
sessionKey: ctx.sessionKey,
|
|
1037
|
-
sessionId: ctx.sessionId
|
|
1038
|
-
}));
|
|
1039
|
-
const effectiveThreadId = response.thread.id;
|
|
1040
|
-
if (effectiveThreadId !== normalizedThreadId) throw new Error(`Codex thread/resume returned ${effectiveThreadId} for ${normalizedThreadId}`);
|
|
1041
|
-
const resumedCwd = response.thread.cwd;
|
|
1042
|
-
if (typeof resumedCwd !== "string") throw new Error(`Codex thread/resume returned no cwd for ${normalizedThreadId}`);
|
|
1043
|
-
const modelProvider = normalizeCodexAppServerBindingModelProvider({
|
|
1044
|
-
authProfileId,
|
|
1045
|
-
modelProvider: response.modelProvider ?? void 0,
|
|
1046
|
-
agentDir: scope.agentDir,
|
|
1047
|
-
config: ctx.config
|
|
1048
|
-
});
|
|
1049
|
-
assertCodexBindingMayBeReplaced(await deps.bindingStore.read(identity), "committing a different resumed thread");
|
|
1050
|
-
if (!await deps.bindingStore.mutate(identity, {
|
|
1051
|
-
kind: "set",
|
|
1052
|
-
binding: {
|
|
1053
|
-
threadId: effectiveThreadId,
|
|
1054
|
-
cwd: resumedCwd,
|
|
1055
|
-
authProfileId,
|
|
1056
|
-
model: response.model,
|
|
1057
|
-
modelProvider,
|
|
1058
|
-
historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
|
|
1059
|
-
}
|
|
1060
|
-
})) throw new Error("Codex thread binding changed while attaching the resumed thread.");
|
|
1061
|
-
return `Attached this OpenClaw session to Codex thread ${formatCodexDisplayText(effectiveThreadId)}.`;
|
|
1062
|
-
});
|
|
985
|
+
function readCodexDiagnosticsScopeCooldownMs(scope, now) {
|
|
986
|
+
const lastSentAt = lastCodexDiagnosticsUploadByScope.get(scope);
|
|
987
|
+
if (!lastSentAt) return 0;
|
|
988
|
+
const remainingMs = Math.max(0, CODEX_DIAGNOSTICS_COOLDOWN_MS - (now - lastSentAt));
|
|
989
|
+
if (remainingMs === 0) lastCodexDiagnosticsUploadByScope.delete(scope);
|
|
990
|
+
return remainingMs;
|
|
1063
991
|
}
|
|
1064
|
-
|
|
1065
|
-
if (!
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
assertCodexBindingMayBeReplaced(await deps.bindingStore.read(sessionBindingIdentity({
|
|
1070
|
-
sessionId: ctx.sessionId,
|
|
1071
|
-
sessionKey: ctx.sessionKey,
|
|
1072
|
-
agentId: scope.agentId,
|
|
1073
|
-
config: ctx.config
|
|
1074
|
-
})), "binding a Codex CLI node session");
|
|
992
|
+
function recordBoundedCodexDiagnosticsCooldown(map, key, maxSize, now) {
|
|
993
|
+
if (!map.has(key)) while (map.size >= maxSize) {
|
|
994
|
+
const oldestKey = map.keys().next().value;
|
|
995
|
+
if (typeof oldestKey !== "string") break;
|
|
996
|
+
map.delete(oldestKey);
|
|
1075
997
|
}
|
|
1076
|
-
|
|
1077
|
-
requestedNode: parsed.host,
|
|
1078
|
-
sessionId: parsed.threadId
|
|
1079
|
-
});
|
|
1080
|
-
if (!resolved.session) return `No Codex CLI session ${formatCodexDisplayText(parsed.threadId)} was found on ${formatCodexDisplayText(parsed.host)}.`;
|
|
1081
|
-
const nodeId = resolved.node.nodeId;
|
|
1082
|
-
if (!nodeId) return "Cannot bind Codex CLI session because the selected node did not include a node id.";
|
|
1083
|
-
const scope = resolveCodexConversationControlScope(ctx);
|
|
1084
|
-
const data = createCodexCliNodeConversationBindingData({
|
|
1085
|
-
nodeId,
|
|
1086
|
-
sessionId: parsed.threadId,
|
|
1087
|
-
agentId: scope.agentId,
|
|
1088
|
-
cwd: resolved.session?.cwd
|
|
1089
|
-
});
|
|
1090
|
-
const summary = `Codex CLI session ${formatCodexDisplayText(parsed.threadId)} on ${formatCodexDisplayText(nodeId)}`;
|
|
1091
|
-
const request = await ctx.requestConversationBinding({
|
|
1092
|
-
summary,
|
|
1093
|
-
detachHint: "/codex detach",
|
|
1094
|
-
data
|
|
1095
|
-
});
|
|
1096
|
-
if (request.status === "bound") return `Bound this conversation to Codex CLI session ${formatCodexDisplayText(parsed.threadId)} on ${formatCodexDisplayText(nodeId)}.`;
|
|
1097
|
-
if (request.status === "pending") return request.reply.text ?? "Codex CLI session binding is pending approval.";
|
|
1098
|
-
return formatCodexDisplayText(request.message);
|
|
998
|
+
map.set(key, now);
|
|
1099
999
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
return (await deps.stopCodexConversationTurn({
|
|
1104
|
-
identity: target.identity,
|
|
1105
|
-
bindingStore: deps.bindingStore,
|
|
1106
|
-
pluginConfig,
|
|
1107
|
-
agentDir: target.agentDir,
|
|
1108
|
-
config: ctx.config
|
|
1109
|
-
})).message;
|
|
1000
|
+
function pruneCodexDiagnosticsCooldowns(now) {
|
|
1001
|
+
pruneCodexDiagnosticsCooldownMap(lastCodexDiagnosticsUploadByThread, now);
|
|
1002
|
+
pruneCodexDiagnosticsCooldownMap(lastCodexDiagnosticsUploadByScope, now);
|
|
1110
1003
|
}
|
|
1111
|
-
|
|
1112
|
-
const
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
bindingStore: deps.bindingStore,
|
|
1117
|
-
message,
|
|
1118
|
-
pluginConfig,
|
|
1119
|
-
agentDir: target.agentDir,
|
|
1120
|
-
config: ctx.config
|
|
1121
|
-
})).message;
|
|
1004
|
+
function pruneCodexDiagnosticsCooldownMap(map, now) {
|
|
1005
|
+
for (const [key, lastSentAt] of map) if (now - lastSentAt >= CODEX_DIAGNOSTICS_COOLDOWN_MS) map.delete(key);
|
|
1006
|
+
}
|
|
1007
|
+
function formatCodexErrorForDisplay(error) {
|
|
1008
|
+
return escapeCodexChatText(truncateUtf16Safe(formatCodexTextForDisplay(error), CODEX_DIAGNOSTICS_ERROR_MAX_CHARS)) || "unknown error";
|
|
1122
1009
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
if (normalized && isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
|
|
1128
|
-
const target = await resolveControlTarget(ctx);
|
|
1129
|
-
if (!target) return "Cannot set Codex model because this command did not include a stable binding identity.";
|
|
1130
|
-
if (!normalized) {
|
|
1131
|
-
const binding = await deps.bindingStore.read(target.identity);
|
|
1132
|
-
return binding?.model ? `Codex model: ${formatCodexDisplayText(binding.model)}` : "Usage: /codex model <model>";
|
|
1133
|
-
}
|
|
1134
|
-
return await deps.setCodexConversationModel({
|
|
1135
|
-
identity: target.identity,
|
|
1136
|
-
bindingStore: deps.bindingStore,
|
|
1137
|
-
pluginConfig,
|
|
1138
|
-
model: normalized,
|
|
1139
|
-
agentDir: target.agentDir,
|
|
1140
|
-
config: ctx.config
|
|
1141
|
-
});
|
|
1010
|
+
function formatCodexResumeCommandForDisplay(threadId) {
|
|
1011
|
+
const safeThreadId = formatCodexTextForDisplay(threadId);
|
|
1012
|
+
if (!CODEX_RESUME_SAFE_THREAD_ID_PATTERN.test(safeThreadId)) return "run codex resume and paste the thread id shown above";
|
|
1013
|
+
return `\`codex resume ${safeThreadId}\``;
|
|
1142
1014
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
const target = await resolveControlTarget(ctx);
|
|
1146
|
-
if (!target) return "Cannot set Codex fast mode because this command did not include a stable binding identity.";
|
|
1147
|
-
const value = args[0];
|
|
1148
|
-
const parsed = parseCodexFastModeArg(value);
|
|
1149
|
-
if (value && parsed == null && value.trim().toLowerCase() !== "status") return "Usage: /codex fast [on|off|status]";
|
|
1150
|
-
return await deps.setCodexConversationFastMode({
|
|
1151
|
-
identity: target.identity,
|
|
1152
|
-
bindingStore: deps.bindingStore,
|
|
1153
|
-
enabled: parsed
|
|
1154
|
-
});
|
|
1015
|
+
function isUnsafeDisplayCodePoint(codePoint) {
|
|
1016
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 || codePoint === 173 || codePoint === 1564 || codePoint === 6158 || codePoint >= 8203 && codePoint <= 8207 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8288 && codePoint <= 8303 || codePoint === 65279 || codePoint >= 65529 && codePoint <= 65531 || codePoint >= 917504 && codePoint <= 917631;
|
|
1155
1017
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
if (
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1018
|
+
function normalizeCodexDiagnosticsScopeField(value) {
|
|
1019
|
+
const normalized = normalizeOptionalString(value);
|
|
1020
|
+
if (!normalized) return;
|
|
1021
|
+
if (normalized.length <= CODEX_DIAGNOSTICS_SCOPE_FIELD_MAX_CHARS) return normalized;
|
|
1022
|
+
return `sha256:${crypto.createHash("sha256").update(normalized).digest("hex")}`;
|
|
1023
|
+
}
|
|
1024
|
+
//#endregion
|
|
1025
|
+
//#region extensions/codex/src/command-handler-deps.ts
|
|
1026
|
+
const defaultCodexCommandDeps = {
|
|
1027
|
+
codexControlRequest,
|
|
1028
|
+
listCodexAppServerModels: listAllCodexAppServerModels,
|
|
1029
|
+
readCodexStatusProbes,
|
|
1030
|
+
requestOptions,
|
|
1031
|
+
safeCodexControlRequest,
|
|
1032
|
+
readCodexComputerUseStatus,
|
|
1033
|
+
installCodexComputerUse,
|
|
1034
|
+
resolveCodexDefaultWorkspaceDir,
|
|
1035
|
+
readCodexConversationActiveTurn,
|
|
1036
|
+
setCodexConversationFastMode,
|
|
1037
|
+
setCodexConversationModel,
|
|
1038
|
+
setCodexConversationPermissions,
|
|
1039
|
+
steerCodexConversationTurn,
|
|
1040
|
+
stopCodexConversationTurn,
|
|
1041
|
+
listCodexCliSessionsOnNode: async () => {
|
|
1042
|
+
throw new Error("Codex CLI node sessions require Gateway node runtime.");
|
|
1043
|
+
},
|
|
1044
|
+
resolveCodexCliSessionForBindingOnNode: async () => {
|
|
1045
|
+
throw new Error("Codex CLI node sessions require Gateway node runtime.");
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
function resolveCodexCommandDeps(overrides) {
|
|
1049
|
+
return {
|
|
1050
|
+
...defaultCodexCommandDeps,
|
|
1051
|
+
...overrides
|
|
1052
|
+
};
|
|
1168
1053
|
}
|
|
1054
|
+
//#endregion
|
|
1055
|
+
//#region extensions/codex/src/command-handler-scope.ts
|
|
1169
1056
|
async function resolveControlTarget(ctx) {
|
|
1170
1057
|
const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
|
|
1171
1058
|
const scope = resolveCodexConversationControlScope(ctx);
|
|
@@ -1226,6 +1113,8 @@ function resolveCodexConversationControlScope(ctx) {
|
|
|
1226
1113
|
agentDir: resolveAgentDir(ctx.config, sessionAgentId)
|
|
1227
1114
|
};
|
|
1228
1115
|
}
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region extensions/codex/src/command-diagnostics.ts
|
|
1229
1118
|
async function handleCodexDiagnosticsFeedback(deps, ctx, pluginConfig, args, commandPrefix) {
|
|
1230
1119
|
if (ctx.senderIsOwner !== true) return { text: "Only an owner can send Codex diagnostics." };
|
|
1231
1120
|
const parsed = parseDiagnosticsArgs(args);
|
|
@@ -1449,316 +1338,463 @@ async function resolveCodexDiagnosticsTargets(deps, ctx) {
|
|
|
1449
1338
|
}
|
|
1450
1339
|
return targets;
|
|
1451
1340
|
}
|
|
1452
|
-
async function resolvePendingCodexDiagnosticsTargets(deps, targets, config) {
|
|
1453
|
-
const resolved = [];
|
|
1454
|
-
for (const target of targets) {
|
|
1455
|
-
const binding = await deps.bindingStore.read(target.identity);
|
|
1456
|
-
if (!binding?.threadId) continue;
|
|
1457
|
-
resolved.push(resolveCodexDiagnosticsTarget(target, binding, config));
|
|
1458
|
-
}
|
|
1459
|
-
return resolved;
|
|
1460
|
-
}
|
|
1461
|
-
function resolveCodexDiagnosticsTarget(target, binding, config) {
|
|
1462
|
-
const candidate = {
|
|
1463
|
-
identity: target.identity,
|
|
1464
|
-
agentDir: target.agentDir,
|
|
1465
|
-
sessionKey: target.sessionKey,
|
|
1466
|
-
sessionId: target.sessionId,
|
|
1467
|
-
channel: target.channel,
|
|
1468
|
-
channelId: target.channelId,
|
|
1469
|
-
accountId: target.accountId,
|
|
1470
|
-
messageThreadId: target.messageThreadId,
|
|
1471
|
-
threadParentId: target.threadParentId
|
|
1472
|
-
};
|
|
1473
|
-
if (binding.connectionScope === "supervision") return {
|
|
1474
|
-
...candidate,
|
|
1475
|
-
threadId: binding.threadId,
|
|
1476
|
-
connectionScope: binding.connectionScope,
|
|
1477
|
-
appServerRuntimeFingerprint: binding.appServerRuntimeFingerprint,
|
|
1478
|
-
pendingSupervisionBranch: binding.pendingSupervisionBranch
|
|
1479
|
-
};
|
|
1480
|
-
const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
|
|
1481
|
-
authProfileId: binding.authProfileId,
|
|
1482
|
-
agentDir: target.agentDir,
|
|
1483
|
-
config
|
|
1484
|
-
});
|
|
1485
|
-
return {
|
|
1486
|
-
...candidate,
|
|
1487
|
-
threadId: binding.threadId,
|
|
1488
|
-
authProfileId
|
|
1489
|
-
};
|
|
1490
|
-
}
|
|
1491
|
-
function codexDiagnosticsTargetsMatch(expected, actual) {
|
|
1492
|
-
const fingerprint = (target) => JSON.stringify([
|
|
1493
|
-
bindingStoreKey(target.identity),
|
|
1494
|
-
target.threadId,
|
|
1495
|
-
target.connectionScope ?? null,
|
|
1496
|
-
target.pendingSupervisionBranch?.connectionFingerprint ?? target.appServerRuntimeFingerprint ?? null,
|
|
1497
|
-
target.authProfileId ?? null
|
|
1498
|
-
]);
|
|
1499
|
-
const expectedTargets = expected.map(fingerprint).toSorted();
|
|
1500
|
-
const actualTargets = actual.map(fingerprint).toSorted();
|
|
1501
|
-
return expectedTargets.length === actualTargets.length && expectedTargets.every((target, index) => target === actualTargets[index]);
|
|
1502
|
-
}
|
|
1503
|
-
function formatCodexDiagnosticsUploadResult(sent, failed) {
|
|
1504
|
-
const lines = [];
|
|
1505
|
-
if (sent.length > 0) {
|
|
1506
|
-
lines.push("Codex diagnostics sent to OpenAI servers:");
|
|
1507
|
-
lines.push(...formatCodexDiagnosticsTargetLines(sent));
|
|
1508
|
-
lines.push("Included Codex logs and spawned Codex subthreads when available.");
|
|
1509
|
-
}
|
|
1510
|
-
if (failed.length > 0) {
|
|
1511
|
-
if (lines.length > 0) lines.push("");
|
|
1512
|
-
lines.push("Could not send Codex diagnostics:");
|
|
1513
|
-
lines.push(...failed.map(({ target, error }) => `${formatCodexDiagnosticsTargetLine(target)}: ${formatCodexErrorForDisplay(error)}`));
|
|
1514
|
-
lines.push("Inspect locally:");
|
|
1515
|
-
lines.push(...failed.map(({ target }) => `- ${formatCodexResumeCommandForDisplay(target.threadId)}`));
|
|
1516
|
-
}
|
|
1517
|
-
return lines.join("\n");
|
|
1518
|
-
}
|
|
1519
|
-
function formatCodexDiagnosticsTargetLines(targets) {
|
|
1520
|
-
return targets.flatMap((target, index) => {
|
|
1521
|
-
const lines = formatCodexDiagnosticsTargetBlock(target, index);
|
|
1522
|
-
return index < targets.length - 1 ? [...lines, ""] : lines;
|
|
1523
|
-
});
|
|
1524
|
-
}
|
|
1525
|
-
function formatCodexDiagnosticsTargetBlock(target, index) {
|
|
1526
|
-
const lines = [`Session ${index + 1}`];
|
|
1527
|
-
if (target.channel) lines.push(`Channel: ${formatCodexValueForDisplay(target.channel)}`);
|
|
1528
|
-
if (target.sessionKey) lines.push(`OpenClaw session key: ${formatCodexCopyableValueForDisplay(target.sessionKey)}`);
|
|
1529
|
-
if (target.sessionId) lines.push(`OpenClaw session id: ${formatCodexCopyableValueForDisplay(target.sessionId)}`);
|
|
1530
|
-
lines.push(`Codex thread id: ${formatCodexCopyableValueForDisplay(target.threadId)}`);
|
|
1531
|
-
lines.push(`Inspect locally: ${formatCodexResumeCommandForDisplay(target.threadId)}`);
|
|
1532
|
-
return lines;
|
|
1533
|
-
}
|
|
1534
|
-
function formatCodexDiagnosticsTargetLine(target) {
|
|
1535
|
-
const parts = [];
|
|
1536
|
-
if (target.channel) parts.push(`channel ${formatCodexValueForDisplay(target.channel)}`);
|
|
1537
|
-
const sessionLabel = target.sessionId || target.sessionKey;
|
|
1538
|
-
if (sessionLabel) parts.push(`OpenClaw session ${formatCodexValueForDisplay(sessionLabel)}`);
|
|
1539
|
-
parts.push(`Codex thread ${formatCodexThreadIdForDisplay(target.threadId)}`);
|
|
1540
|
-
return `- ${parts.join(", ")}`;
|
|
1541
|
-
}
|
|
1542
|
-
function normalizeDiagnosticsReason(note) {
|
|
1543
|
-
const normalized = normalizeOptionalString(note);
|
|
1544
|
-
return normalized ? truncateUtf16Safe(normalized, CODEX_DIAGNOSTICS_REASON_MAX_CHARS) : void 0;
|
|
1545
|
-
}
|
|
1546
|
-
function parseDiagnosticsArgs(args) {
|
|
1547
|
-
const [action, token, ...extra] = splitArgs(args);
|
|
1548
|
-
const normalizedAction = action?.toLowerCase();
|
|
1549
|
-
if ((normalizedAction === "confirm" || normalizedAction === "--confirm") && token && extra.length === 0) return {
|
|
1550
|
-
action: "confirm",
|
|
1551
|
-
token
|
|
1552
|
-
};
|
|
1553
|
-
if ((normalizedAction === "cancel" || normalizedAction === "--cancel") && token && extra.length === 0) return {
|
|
1554
|
-
action: "cancel",
|
|
1555
|
-
token
|
|
1556
|
-
};
|
|
1557
|
-
if (normalizedAction === "confirm" || normalizedAction === "--confirm" || normalizedAction === "cancel" || normalizedAction === "--cancel") return { action: "usage" };
|
|
1558
|
-
return {
|
|
1559
|
-
action: "request",
|
|
1560
|
-
note: args
|
|
1561
|
-
};
|
|
1562
|
-
}
|
|
1563
|
-
function formatDiagnosticsUsage(commandPrefix) {
|
|
1564
|
-
return [
|
|
1565
|
-
`Usage: ${commandPrefix} [note]`,
|
|
1566
|
-
`Usage: ${commandPrefix} confirm <token>`,
|
|
1567
|
-
`Usage: ${commandPrefix} cancel <token>`
|
|
1568
|
-
].join("\n");
|
|
1569
|
-
}
|
|
1570
|
-
function createCodexDiagnosticsConfirmation(params) {
|
|
1571
|
-
prunePendingCodexDiagnosticsConfirmations(params.now);
|
|
1572
|
-
if (!pendingCodexDiagnosticsConfirmationTokensByScope.has(params.scopeKey) && pendingCodexDiagnosticsConfirmationTokensByScope.size >= CODEX_DIAGNOSTICS_CONFIRMATION_MAX_SCOPES) {
|
|
1573
|
-
const oldestScopeKey = pendingCodexDiagnosticsConfirmationTokensByScope.keys().next().value;
|
|
1574
|
-
if (typeof oldestScopeKey === "string") deletePendingCodexDiagnosticsConfirmationScope(oldestScopeKey);
|
|
1575
|
-
}
|
|
1576
|
-
const scopeTokens = pendingCodexDiagnosticsConfirmationTokensByScope.get(params.scopeKey) ?? [];
|
|
1577
|
-
while (scopeTokens.length >= CODEX_DIAGNOSTICS_CONFIRMATION_MAX_REQUESTS_PER_SCOPE) {
|
|
1578
|
-
const oldestToken = scopeTokens.shift();
|
|
1579
|
-
if (!oldestToken) break;
|
|
1580
|
-
pendingCodexDiagnosticsConfirmations.delete(oldestToken);
|
|
1581
|
-
}
|
|
1582
|
-
const token = crypto.randomBytes(6).toString("hex");
|
|
1583
|
-
scopeTokens.push(token);
|
|
1584
|
-
pendingCodexDiagnosticsConfirmationTokensByScope.set(params.scopeKey, scopeTokens);
|
|
1585
|
-
pendingCodexDiagnosticsConfirmations.set(token, {
|
|
1586
|
-
token,
|
|
1587
|
-
targets: params.targets,
|
|
1588
|
-
note: params.note,
|
|
1589
|
-
senderId: params.senderId,
|
|
1590
|
-
channel: params.channel,
|
|
1591
|
-
accountId: params.accountId,
|
|
1592
|
-
channelId: params.channelId,
|
|
1593
|
-
messageThreadId: params.messageThreadId,
|
|
1594
|
-
threadParentId: params.threadParentId,
|
|
1595
|
-
sessionKey: params.sessionKey,
|
|
1596
|
-
scopeKey: params.scopeKey,
|
|
1597
|
-
...params.privateRouted === void 0 ? {} : { privateRouted: params.privateRouted },
|
|
1598
|
-
createdAt: params.now
|
|
1599
|
-
});
|
|
1600
|
-
return token;
|
|
1601
|
-
}
|
|
1602
|
-
function readCodexDiagnosticsConfirmationScope(ctx) {
|
|
1603
|
-
return {
|
|
1604
|
-
accountId: normalizeCodexDiagnosticsScopeField(ctx.accountId),
|
|
1605
|
-
channelId: normalizeCodexDiagnosticsScopeField(ctx.channelId),
|
|
1606
|
-
messageThreadId: typeof ctx.messageThreadId === "string" || typeof ctx.messageThreadId === "number" ? normalizeCodexDiagnosticsScopeField(String(ctx.messageThreadId)) : void 0,
|
|
1607
|
-
threadParentId: normalizeCodexDiagnosticsScopeField(ctx.threadParentId),
|
|
1608
|
-
sessionKey: normalizeCodexDiagnosticsScopeField(ctx.sessionKey)
|
|
1609
|
-
};
|
|
1610
|
-
}
|
|
1611
|
-
function readCodexDiagnosticsScopeMismatch(pending, ctx) {
|
|
1612
|
-
const current = readCodexDiagnosticsConfirmationScope(ctx);
|
|
1613
|
-
if (pending.accountId !== current.accountId) return {
|
|
1614
|
-
confirmMessage: "This Codex diagnostics confirmation belongs to a different account.",
|
|
1615
|
-
cancelMessage: "This Codex diagnostics confirmation belongs to a different account."
|
|
1616
|
-
};
|
|
1617
|
-
if (pending.privateRouted) return;
|
|
1618
|
-
if (pending.channelId !== current.channelId) return {
|
|
1619
|
-
confirmMessage: "This Codex diagnostics confirmation belongs to a different channel instance.",
|
|
1620
|
-
cancelMessage: "This Codex diagnostics confirmation belongs to a different channel instance."
|
|
1621
|
-
};
|
|
1622
|
-
if (pending.messageThreadId !== current.messageThreadId) return {
|
|
1623
|
-
confirmMessage: "This Codex diagnostics confirmation belongs to a different thread.",
|
|
1624
|
-
cancelMessage: "This Codex diagnostics confirmation belongs to a different thread."
|
|
1341
|
+
async function resolvePendingCodexDiagnosticsTargets(deps, targets, config) {
|
|
1342
|
+
const resolved = [];
|
|
1343
|
+
for (const target of targets) {
|
|
1344
|
+
const binding = await deps.bindingStore.read(target.identity);
|
|
1345
|
+
if (!binding?.threadId) continue;
|
|
1346
|
+
resolved.push(resolveCodexDiagnosticsTarget(target, binding, config));
|
|
1347
|
+
}
|
|
1348
|
+
return resolved;
|
|
1349
|
+
}
|
|
1350
|
+
function resolveCodexDiagnosticsTarget(target, binding, config) {
|
|
1351
|
+
const candidate = {
|
|
1352
|
+
identity: target.identity,
|
|
1353
|
+
agentDir: target.agentDir,
|
|
1354
|
+
sessionKey: target.sessionKey,
|
|
1355
|
+
sessionId: target.sessionId,
|
|
1356
|
+
channel: target.channel,
|
|
1357
|
+
channelId: target.channelId,
|
|
1358
|
+
accountId: target.accountId,
|
|
1359
|
+
messageThreadId: target.messageThreadId,
|
|
1360
|
+
threadParentId: target.threadParentId
|
|
1625
1361
|
};
|
|
1626
|
-
if (
|
|
1627
|
-
|
|
1628
|
-
|
|
1362
|
+
if (binding.connectionScope === "supervision") return {
|
|
1363
|
+
...candidate,
|
|
1364
|
+
threadId: binding.threadId,
|
|
1365
|
+
connectionScope: binding.connectionScope,
|
|
1366
|
+
appServerRuntimeFingerprint: binding.appServerRuntimeFingerprint,
|
|
1367
|
+
pendingSupervisionBranch: binding.pendingSupervisionBranch
|
|
1629
1368
|
};
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1369
|
+
const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
|
|
1370
|
+
authProfileId: binding.authProfileId,
|
|
1371
|
+
agentDir: target.agentDir,
|
|
1372
|
+
config
|
|
1373
|
+
});
|
|
1374
|
+
return {
|
|
1375
|
+
...candidate,
|
|
1376
|
+
threadId: binding.threadId,
|
|
1377
|
+
authProfileId
|
|
1633
1378
|
};
|
|
1634
1379
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
if (!scopeTokens) return;
|
|
1648
|
-
const tokenIndex = scopeTokens.indexOf(token);
|
|
1649
|
-
if (tokenIndex >= 0) scopeTokens.splice(tokenIndex, 1);
|
|
1650
|
-
if (scopeTokens.length === 0) pendingCodexDiagnosticsConfirmationTokensByScope.delete(pending.scopeKey);
|
|
1380
|
+
//#endregion
|
|
1381
|
+
//#region extensions/codex/src/command-handler-bindings.ts
|
|
1382
|
+
function isCurrentSessionModelSelectionLocked(ctx) {
|
|
1383
|
+
const sessionKey = ctx.sessionKey?.trim();
|
|
1384
|
+
if (!sessionKey) return false;
|
|
1385
|
+
const { agentId } = resolveCodexConversationControlScope(ctx);
|
|
1386
|
+
return isModelSelectionLocked(getSessionEntry({
|
|
1387
|
+
storePath: resolveStorePath(ctx.config.session?.store, { agentId }),
|
|
1388
|
+
sessionKey,
|
|
1389
|
+
hydrateSkillPromptRefs: false,
|
|
1390
|
+
readConsistency: "latest"
|
|
1391
|
+
}));
|
|
1651
1392
|
}
|
|
1652
|
-
function
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1393
|
+
async function bindConversation(deps, ctx, pluginConfig, args) {
|
|
1394
|
+
const parsed = parseBindArgs(args);
|
|
1395
|
+
if (parsed.help) return { text: "Usage: /codex bind [thread-id] [--cwd <path>] [--model <model>] [--provider <provider>]" };
|
|
1396
|
+
if (isCurrentSessionModelSelectionLocked(ctx)) return { text: MODEL_SELECTION_LOCKED_MESSAGE };
|
|
1397
|
+
const scope = resolveCodexConversationControlScope(ctx);
|
|
1398
|
+
const workspaceDir = parsed.cwd ?? deps.resolveCodexDefaultWorkspaceDir(pluginConfig);
|
|
1399
|
+
const currentConversation = await ctx.getCurrentConversationBinding();
|
|
1400
|
+
const currentConversationData = readCodexConversationBindingData(currentConversation);
|
|
1401
|
+
const bindingId = currentConversationData?.kind === "codex-app-server-session" ? currentConversationData.bindingId : currentConversation ? `conversation-${currentConversation.bindingId}` : void 0;
|
|
1402
|
+
const sessionOwner = ctx.sessionId ? sessionBindingIdentity({
|
|
1403
|
+
sessionId: ctx.sessionId,
|
|
1404
|
+
sessionKey: ctx.sessionKey,
|
|
1405
|
+
agentId: scope.agentId,
|
|
1406
|
+
config: ctx.config
|
|
1407
|
+
}) : void 0;
|
|
1408
|
+
const currentOwner = currentConversationData?.kind === "codex-app-server-session" ? conversationBindingIdentity(currentConversationData.bindingId) : sessionOwner;
|
|
1409
|
+
const existingBinding = currentOwner ? await deps.bindingStore.read(currentOwner) : void 0;
|
|
1410
|
+
assertCodexBindingMayBeReplaced(existingBinding, "binding this conversation to another thread");
|
|
1411
|
+
const sessionSource = sessionOwner && existingBinding ? {
|
|
1412
|
+
agentId: sessionOwner.agentId,
|
|
1413
|
+
sessionId: sessionOwner.sessionId,
|
|
1414
|
+
threadId: existingBinding.threadId,
|
|
1415
|
+
...sessionOwner.sessionKey ? { sessionKey: sessionOwner.sessionKey } : {}
|
|
1416
|
+
} : void 0;
|
|
1417
|
+
const authProfileId = existingBinding?.authProfileId;
|
|
1418
|
+
const data = createCodexConversationBindingData({
|
|
1419
|
+
bindingId,
|
|
1420
|
+
workspaceDir,
|
|
1421
|
+
agentId: scope.agentId,
|
|
1422
|
+
agentDir: scope.agentDir,
|
|
1423
|
+
source: currentConversationData?.kind === "codex-app-server-session" ? currentConversationData.source : sessionSource,
|
|
1424
|
+
start: {
|
|
1425
|
+
id: crypto.randomUUID(),
|
|
1426
|
+
threadId: parsed.threadId,
|
|
1427
|
+
model: parsed.model,
|
|
1428
|
+
modelProvider: parsed.provider,
|
|
1429
|
+
authProfileId
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
const threadLabel = parsed.threadId ?? "a new thread";
|
|
1433
|
+
const request = await ctx.requestConversationBinding({
|
|
1434
|
+
summary: `Codex app-server thread ${formatCodexDisplayText(threadLabel)} in ${formatCodexDisplayText(workspaceDir)}`,
|
|
1435
|
+
detachHint: "/codex detach",
|
|
1436
|
+
data
|
|
1437
|
+
});
|
|
1438
|
+
if (request.status === "pending") return request.reply;
|
|
1439
|
+
if (request.status === "error") return { text: formatCodexDisplayText(request.message) };
|
|
1440
|
+
return { text: `Bound this conversation to ${formatCodexDisplayText(threadLabel)} in ${formatCodexDisplayText(workspaceDir)}. The next message will initialize it.` };
|
|
1656
1441
|
}
|
|
1657
|
-
function
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1442
|
+
async function detachConversation(deps, ctx) {
|
|
1443
|
+
if (isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
|
|
1444
|
+
const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
|
|
1445
|
+
if (data?.kind === "codex-app-server-session") assertCodexBindingMayBeReplaced(await deps.bindingStore.read(conversationBindingIdentity(data.bindingId)), "detaching its conversation binding");
|
|
1446
|
+
const detached = await ctx.detachConversationBinding();
|
|
1447
|
+
if (data?.kind === "codex-app-server-session") await deps.bindingStore.mutate(conversationBindingIdentity(data.bindingId), { kind: "clear" });
|
|
1448
|
+
return detached.removed ? "Detached this conversation from Codex." : "No Codex conversation binding was attached.";
|
|
1661
1449
|
}
|
|
1662
|
-
function
|
|
1663
|
-
|
|
1450
|
+
async function describeConversationBinding(deps, ctx) {
|
|
1451
|
+
const current = await ctx.getCurrentConversationBinding();
|
|
1452
|
+
const data = readCodexConversationBindingData(current);
|
|
1453
|
+
if (!current || !data) return "No Codex conversation binding is attached.";
|
|
1454
|
+
if (data.kind === "codex-cli-node-session") return [
|
|
1455
|
+
"Codex conversation binding:",
|
|
1456
|
+
"- Mode: Codex CLI node session",
|
|
1457
|
+
`- Node: ${formatCodexDisplayText(data.nodeId)}`,
|
|
1458
|
+
`- Session: ${formatCodexDisplayText(data.sessionId)}`,
|
|
1459
|
+
`- Workspace: ${formatCodexDisplayText(data.cwd ?? "unknown")}`,
|
|
1460
|
+
"- Active run: not tracked"
|
|
1461
|
+
].join("\n");
|
|
1462
|
+
const identity = conversationBindingIdentity(data.bindingId);
|
|
1463
|
+
const threadBinding = await deps.bindingStore.read(identity);
|
|
1464
|
+
const active = deps.readCodexConversationActiveTurn(identity);
|
|
1465
|
+
return [
|
|
1466
|
+
"Codex conversation binding:",
|
|
1467
|
+
`- Thread: ${formatCodexDisplayText(threadBinding?.threadId ?? "unknown")}`,
|
|
1468
|
+
`- Workspace: ${formatCodexDisplayText(data.workspaceDir)}`,
|
|
1469
|
+
`- Model: ${formatCodexDisplayText(threadBinding?.model ?? "default")}`,
|
|
1470
|
+
`- Fast: ${isCodexFastServiceTier(threadBinding?.serviceTier) ? "on" : "off"}`,
|
|
1471
|
+
`- Permissions: ${threadBinding ? formatPermissionsMode(threadBinding) : "default"}`,
|
|
1472
|
+
`- Active run: ${formatCodexDisplayText(active ? active.turnId : "none")}`,
|
|
1473
|
+
`- Binding: ${formatCodexDisplayText(data.bindingId)}`
|
|
1474
|
+
].join("\n");
|
|
1664
1475
|
}
|
|
1665
|
-
function
|
|
1666
|
-
|
|
1476
|
+
async function buildThreads(deps, ctx, pluginConfig, filter) {
|
|
1477
|
+
const scope = await resolveCommandAppServerScope(deps, ctx, pluginConfig);
|
|
1478
|
+
return formatThreads(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listThreads, {
|
|
1479
|
+
limit: 10,
|
|
1480
|
+
...filter.trim() ? { searchTerm: filter.trim() } : {}
|
|
1481
|
+
}, {
|
|
1482
|
+
config: ctx.config,
|
|
1483
|
+
...scope
|
|
1484
|
+
}));
|
|
1667
1485
|
}
|
|
1668
|
-
function
|
|
1669
|
-
|
|
1486
|
+
async function buildCodexCliSessions(deps, args) {
|
|
1487
|
+
const parsed = parseCodexCliSessionsArgs(args);
|
|
1488
|
+
if (parsed.help || !parsed.host) return "Usage: /codex sessions --host <node> [filter] [--limit <n>]";
|
|
1489
|
+
return formatCodexCliSessions(await deps.listCodexCliSessionsOnNode({
|
|
1490
|
+
requestedNode: parsed.host,
|
|
1491
|
+
filter: parsed.filter,
|
|
1492
|
+
limit: parsed.limit
|
|
1493
|
+
}));
|
|
1670
1494
|
}
|
|
1671
|
-
function
|
|
1672
|
-
const
|
|
1673
|
-
|
|
1674
|
-
return
|
|
1495
|
+
async function resumeThread(deps, ctx, pluginConfig, args) {
|
|
1496
|
+
const parsed = parseResumeArgs(args);
|
|
1497
|
+
const normalizedThreadId = parsed.threadId?.trim();
|
|
1498
|
+
if (parsed.help) return args.includes("--help") || args.includes("-h") || parsed.host ? "Usage: /codex resume <thread-id>\nUsage: /codex resume <session-id> --host <node> --bind here" : "Usage: /codex resume <thread-id>";
|
|
1499
|
+
if (parsed.host) return await bindCodexCliNodeSession(deps, ctx, parsed);
|
|
1500
|
+
if (!normalizedThreadId || args.length !== 1) return "Usage: /codex resume <thread-id>";
|
|
1501
|
+
if (isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
|
|
1502
|
+
if (!ctx.sessionId) return "Cannot attach a Codex thread because this command did not include an OpenClaw session id.";
|
|
1503
|
+
const scope = resolveCodexConversationControlScope(ctx);
|
|
1504
|
+
const identity = sessionBindingIdentity({
|
|
1505
|
+
sessionId: ctx.sessionId,
|
|
1506
|
+
sessionKey: ctx.sessionKey,
|
|
1507
|
+
agentId: scope.agentId,
|
|
1508
|
+
config: ctx.config
|
|
1509
|
+
});
|
|
1510
|
+
return await deps.bindingStore.withLease(identity, async () => {
|
|
1511
|
+
if (!await reclaimCurrentCodexSessionGeneration({
|
|
1512
|
+
bindingStore: deps.bindingStore,
|
|
1513
|
+
identity,
|
|
1514
|
+
config: ctx.config
|
|
1515
|
+
})) throw createCodexSessionGenerationSupersededError(identity.sessionId);
|
|
1516
|
+
const currentBinding = await deps.bindingStore.read(identity);
|
|
1517
|
+
assertCodexBindingMayBeReplaced(currentBinding, "attaching a different resumed thread");
|
|
1518
|
+
const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
|
|
1519
|
+
authProfileId: currentBinding?.authProfileId,
|
|
1520
|
+
agentDir: scope.agentDir,
|
|
1521
|
+
config: ctx.config
|
|
1522
|
+
});
|
|
1523
|
+
const response = assertCodexThreadResumeResponse(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.resumeThread, {
|
|
1524
|
+
threadId: normalizedThreadId,
|
|
1525
|
+
excludeTurns: true
|
|
1526
|
+
}, {
|
|
1527
|
+
config: ctx.config,
|
|
1528
|
+
agentDir: scope.agentDir,
|
|
1529
|
+
authProfileId,
|
|
1530
|
+
sessionKey: ctx.sessionKey,
|
|
1531
|
+
sessionId: ctx.sessionId
|
|
1532
|
+
}));
|
|
1533
|
+
const effectiveThreadId = response.thread.id;
|
|
1534
|
+
if (effectiveThreadId !== normalizedThreadId) throw new Error(`Codex thread/resume returned ${effectiveThreadId} for ${normalizedThreadId}`);
|
|
1535
|
+
const resumedCwd = response.thread.cwd;
|
|
1536
|
+
if (typeof resumedCwd !== "string") throw new Error(`Codex thread/resume returned no cwd for ${normalizedThreadId}`);
|
|
1537
|
+
const modelProvider = normalizeCodexAppServerBindingModelProvider({
|
|
1538
|
+
authProfileId,
|
|
1539
|
+
modelProvider: response.modelProvider ?? void 0,
|
|
1540
|
+
agentDir: scope.agentDir,
|
|
1541
|
+
config: ctx.config
|
|
1542
|
+
});
|
|
1543
|
+
assertCodexBindingMayBeReplaced(await deps.bindingStore.read(identity), "committing a different resumed thread");
|
|
1544
|
+
if (!await deps.bindingStore.mutate(identity, {
|
|
1545
|
+
kind: "set",
|
|
1546
|
+
binding: {
|
|
1547
|
+
threadId: effectiveThreadId,
|
|
1548
|
+
cwd: resumedCwd,
|
|
1549
|
+
authProfileId,
|
|
1550
|
+
model: response.model,
|
|
1551
|
+
modelProvider,
|
|
1552
|
+
historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
|
|
1553
|
+
}
|
|
1554
|
+
})) throw new Error("Codex thread binding changed while attaching the resumed thread.");
|
|
1555
|
+
return `Attached this OpenClaw session to Codex thread ${formatCodexDisplayText(effectiveThreadId)}.`;
|
|
1556
|
+
});
|
|
1675
1557
|
}
|
|
1676
|
-
function
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1558
|
+
async function bindCodexCliNodeSession(deps, ctx, parsed) {
|
|
1559
|
+
if (!parsed.threadId || !parsed.host || parsed.bindHere !== true) return "Usage: /codex resume <session-id> --host <node> --bind here";
|
|
1560
|
+
if (isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
|
|
1561
|
+
if (ctx.sessionId) {
|
|
1562
|
+
const scope = resolveCodexConversationControlScope(ctx);
|
|
1563
|
+
assertCodexBindingMayBeReplaced(await deps.bindingStore.read(sessionBindingIdentity({
|
|
1564
|
+
sessionId: ctx.sessionId,
|
|
1565
|
+
sessionKey: ctx.sessionKey,
|
|
1566
|
+
agentId: scope.agentId,
|
|
1567
|
+
config: ctx.config
|
|
1568
|
+
})), "binding a Codex CLI node session");
|
|
1681
1569
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1570
|
+
const resolved = await deps.resolveCodexCliSessionForBindingOnNode({
|
|
1571
|
+
requestedNode: parsed.host,
|
|
1572
|
+
sessionId: parsed.threadId
|
|
1573
|
+
});
|
|
1574
|
+
if (!resolved.session) return `No Codex CLI session ${formatCodexDisplayText(parsed.threadId)} was found on ${formatCodexDisplayText(parsed.host)}.`;
|
|
1575
|
+
const nodeId = resolved.node.nodeId;
|
|
1576
|
+
if (!nodeId) return "Cannot bind Codex CLI session because the selected node did not include a node id.";
|
|
1577
|
+
const scope = resolveCodexConversationControlScope(ctx);
|
|
1578
|
+
const data = createCodexCliNodeConversationBindingData({
|
|
1579
|
+
nodeId,
|
|
1580
|
+
sessionId: parsed.threadId,
|
|
1581
|
+
agentId: scope.agentId,
|
|
1582
|
+
cwd: resolved.session?.cwd
|
|
1583
|
+
});
|
|
1584
|
+
const summary = `Codex CLI session ${formatCodexDisplayText(parsed.threadId)} on ${formatCodexDisplayText(nodeId)}`;
|
|
1585
|
+
const request = await ctx.requestConversationBinding({
|
|
1586
|
+
summary,
|
|
1587
|
+
detachHint: "/codex detach",
|
|
1588
|
+
data
|
|
1589
|
+
});
|
|
1590
|
+
if (request.status === "bound") return `Bound this conversation to Codex CLI session ${formatCodexDisplayText(parsed.threadId)} on ${formatCodexDisplayText(nodeId)}.`;
|
|
1591
|
+
if (request.status === "pending") return request.reply.text ?? "Codex CLI session binding is pending approval.";
|
|
1592
|
+
return formatCodexDisplayText(request.message);
|
|
1684
1593
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1594
|
+
//#endregion
|
|
1595
|
+
//#region extensions/codex/src/command-handler-actions.ts
|
|
1596
|
+
const CODEX_NATIVE_EXECUTION_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
1597
|
+
"bind",
|
|
1598
|
+
"resume",
|
|
1599
|
+
"steer",
|
|
1600
|
+
"model",
|
|
1601
|
+
"fast",
|
|
1602
|
+
"permissions",
|
|
1603
|
+
"compact",
|
|
1604
|
+
"review",
|
|
1605
|
+
"goal"
|
|
1606
|
+
]);
|
|
1607
|
+
const CODEX_NATIVE_CONTROL_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
1608
|
+
...CODEX_NATIVE_EXECUTION_SUBCOMMANDS,
|
|
1609
|
+
"detach",
|
|
1610
|
+
"unbind",
|
|
1611
|
+
"stop"
|
|
1612
|
+
]);
|
|
1613
|
+
function resolveCodexNativeCommandSandboxBlock(ctx, subcommand, args) {
|
|
1614
|
+
if (isReadOnlyCodexGoalCommand(subcommand, args)) return;
|
|
1615
|
+
if (!CODEX_NATIVE_EXECUTION_SUBCOMMANDS.has(subcommand)) return;
|
|
1616
|
+
if (returnsBeforeNativeCodexExecution(subcommand, args)) return;
|
|
1617
|
+
if (isCodexCliNodeResumeBind(subcommand, args)) return resolveCodexNativeSandboxBlock({
|
|
1618
|
+
config: ctx.config,
|
|
1619
|
+
sessionKey: ctx.sessionKey,
|
|
1620
|
+
sessionId: ctx.sessionId,
|
|
1621
|
+
surface: `/${["codex", subcommand].join(" ")}`
|
|
1622
|
+
});
|
|
1623
|
+
return resolveCodexNativeExecutionBlock({
|
|
1624
|
+
config: ctx.config,
|
|
1625
|
+
agentId: ctx.agentId,
|
|
1626
|
+
sessionKey: ctx.sessionKey,
|
|
1627
|
+
sessionId: ctx.sessionId,
|
|
1628
|
+
surface: `/${["codex", subcommand].join(" ")}`
|
|
1629
|
+
});
|
|
1687
1630
|
}
|
|
1688
|
-
function
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
if (remainingMs === 0) lastCodexDiagnosticsUploadByThread.delete(threadId);
|
|
1693
|
-
return remainingMs;
|
|
1631
|
+
function isReadOnlyCodexGoalCommand(subcommand, args) {
|
|
1632
|
+
if (subcommand !== "goal" || args.length > 1) return false;
|
|
1633
|
+
const action = (args[0] ?? "status").toLowerCase();
|
|
1634
|
+
return action === "status" || action === "get";
|
|
1694
1635
|
}
|
|
1695
|
-
function
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1636
|
+
function returnsBeforeNativeCodexExecution(subcommand, args) {
|
|
1637
|
+
switch (subcommand) {
|
|
1638
|
+
case "bind": return parseBindArgs([...args]).help === true;
|
|
1639
|
+
case "resume": return returnsBeforeNativeCodexResume(args);
|
|
1640
|
+
case "steer": return args.join(" ").trim() === "";
|
|
1641
|
+
case "model": return args.length === 0 || args.length > 1;
|
|
1642
|
+
case "fast": return args.length === 0 || args.length > 1 || parseCodexFastModeArg(args[0]) === void 0;
|
|
1643
|
+
case "permissions": return args.length === 0 || args.length > 1 || parseCodexPermissionsModeArg(args[0]) === void 0;
|
|
1644
|
+
case "compact":
|
|
1645
|
+
case "review":
|
|
1646
|
+
case "detach":
|
|
1647
|
+
case "unbind":
|
|
1648
|
+
case "stop": return args.length > 0;
|
|
1649
|
+
default: return false;
|
|
1702
1650
|
}
|
|
1703
|
-
const scopeCooldownMs = readCodexDiagnosticsScopeCooldownMs(options.cooldownScope ?? readCodexDiagnosticsCooldownScope(ctx), now);
|
|
1704
|
-
if (scopeCooldownMs > 0) return `Codex diagnostics were already sent for this account or channel recently. Try again in ${Math.ceil(scopeCooldownMs / 1e3)}s.`;
|
|
1705
1651
|
}
|
|
1706
|
-
function
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
if (remainingMs === 0) lastCodexDiagnosticsUploadByScope.delete(scope);
|
|
1711
|
-
return remainingMs;
|
|
1652
|
+
function isCodexCliNodeResumeBind(subcommand, args) {
|
|
1653
|
+
if (subcommand !== "resume") return false;
|
|
1654
|
+
const parsed = parseResumeArgs([...args]);
|
|
1655
|
+
return Boolean(parsed.host && parsed.threadId && parsed.bindHere === true && !parsed.help);
|
|
1712
1656
|
}
|
|
1713
|
-
function
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1657
|
+
function returnsBeforeNativeCodexResume(args) {
|
|
1658
|
+
const parsed = parseResumeArgs([...args]);
|
|
1659
|
+
const normalizedThreadId = parsed.threadId?.trim();
|
|
1660
|
+
if (parsed.help) return true;
|
|
1661
|
+
if (parsed.host) return !normalizedThreadId || parsed.bindHere !== true;
|
|
1662
|
+
return !normalizedThreadId || args.length !== 1;
|
|
1717
1663
|
}
|
|
1718
|
-
function
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1664
|
+
async function handleComputerUseCommand(deps, ctx, pluginConfig, args) {
|
|
1665
|
+
const parsed = parseComputerUseArgs(args);
|
|
1666
|
+
if (parsed.help) return ["Usage: /codex computer-use [status|install] [--source <marketplace-source>] [--marketplace-path <path>] [--marketplace <name>]", "Checks or installs the configured Codex Computer Use plugin through app-server."].join("\n");
|
|
1667
|
+
if (Object.keys(parsed.persistentIdentity).length > 0) return formatComputerUsePersistentIdentityMigration(parsed);
|
|
1668
|
+
if (parsed.action === "install" && !canMutateCodexHost(ctx)) return "Only an owner or operator.admin gateway client can configure Codex Computer Use.";
|
|
1669
|
+
const { agentDir } = resolveCodexConversationControlScope(ctx);
|
|
1670
|
+
const params = {
|
|
1671
|
+
pluginConfig,
|
|
1672
|
+
config: ctx.config,
|
|
1673
|
+
agentDir,
|
|
1674
|
+
forceEnable: parsed.action === "install" || parsed.hasOverrides,
|
|
1675
|
+
...Object.keys(parsed.overrides).length > 0 ? { overrides: parsed.overrides } : {}
|
|
1676
|
+
};
|
|
1677
|
+
if (parsed.action === "install") return formatComputerUseStatus(await deps.installCodexComputerUse(params));
|
|
1678
|
+
return formatComputerUseStatus(await deps.readCodexComputerUseStatus(params));
|
|
1725
1679
|
}
|
|
1726
|
-
function
|
|
1727
|
-
const
|
|
1728
|
-
const
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1680
|
+
async function handleNativeGoal(deps, ctx, pluginConfig, args) {
|
|
1681
|
+
const action = (args[0] ?? "status").toLowerCase();
|
|
1682
|
+
const objective = args.slice(1).join(" ").trim();
|
|
1683
|
+
const target = await resolveControlTarget(ctx);
|
|
1684
|
+
if (!target) return "Cannot manage the Codex goal because this command has no stable binding identity.";
|
|
1685
|
+
const binding = await deps.bindingStore.read(target.identity);
|
|
1686
|
+
if (!binding?.threadId) return "No Codex thread is attached to this OpenClaw session yet.";
|
|
1687
|
+
const connection = resolveCodexBindingAppServerConnection({
|
|
1688
|
+
binding,
|
|
1689
|
+
authProfileId: binding.authProfileId,
|
|
1690
|
+
pluginConfig
|
|
1736
1691
|
});
|
|
1737
|
-
|
|
1692
|
+
const goalRequestOptions = {
|
|
1693
|
+
agentDir: target.agentDir,
|
|
1694
|
+
authProfileId: connection.clientAuthProfileId,
|
|
1695
|
+
config: ctx.config,
|
|
1696
|
+
...connection.usesSupervisionConnection ? { startOptions: connection.appServer.start } : {}
|
|
1697
|
+
};
|
|
1698
|
+
if (action === "status" || action === "get") {
|
|
1699
|
+
if (args.length > 1) return "Usage: /codex goal [status]";
|
|
1700
|
+
return formatNativeGoal(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.getThreadGoal, { threadId: binding.threadId }, goalRequestOptions));
|
|
1701
|
+
}
|
|
1702
|
+
if (action === "clear") {
|
|
1703
|
+
if (args.length > 1) return "Usage: /codex goal clear";
|
|
1704
|
+
const response = await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.clearThreadGoal, { threadId: binding.threadId }, goalRequestOptions);
|
|
1705
|
+
return isJsonObject(response) && response.cleared === true ? "Cleared the Codex goal." : "No Codex goal was active.";
|
|
1706
|
+
}
|
|
1707
|
+
const requestedStatus = action === "pause" ? "paused" : action === "resume" ? "active" : action === "block" ? "blocked" : action === "complete" ? "complete" : void 0;
|
|
1708
|
+
const isObjectiveUpdate = action === "set";
|
|
1709
|
+
if (!requestedStatus && !isObjectiveUpdate || isObjectiveUpdate && !objective) return "Usage: /codex goal [status|set <objective>|pause|resume|block|complete|clear]";
|
|
1710
|
+
if (requestedStatus && args.length > 1) return `Usage: /codex goal ${action}`;
|
|
1711
|
+
return formatNativeGoal(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.setThreadGoal, {
|
|
1712
|
+
threadId: binding.threadId,
|
|
1713
|
+
...objective ? { objective } : {},
|
|
1714
|
+
...requestedStatus ? { status: requestedStatus } : {}
|
|
1715
|
+
}, goalRequestOptions));
|
|
1738
1716
|
}
|
|
1739
|
-
function
|
|
1740
|
-
|
|
1741
|
-
|
|
1717
|
+
function formatNativeGoal(response) {
|
|
1718
|
+
const goal = isJsonObject(response) && isJsonObject(response.goal) ? response.goal : void 0;
|
|
1719
|
+
if (!goal) return "No Codex goal is active.";
|
|
1720
|
+
const objective = readString$1(goal, "objective") ?? "unknown";
|
|
1721
|
+
const status = readString$1(goal, "status") ?? "unknown";
|
|
1722
|
+
const tokensUsed = typeof goal.tokensUsed === "number" ? goal.tokensUsed : 0;
|
|
1723
|
+
const tokenBudget = typeof goal.tokenBudget === "number" ? goal.tokenBudget : void 0;
|
|
1724
|
+
return [
|
|
1725
|
+
`Codex goal: ${formatCodexDisplayText(objective)}`,
|
|
1726
|
+
`- Status: ${formatCodexDisplayText(status)}`,
|
|
1727
|
+
`- Tokens: ${tokensUsed}${tokenBudget === void 0 ? "" : ` / ${tokenBudget}`}`
|
|
1728
|
+
].join("\n");
|
|
1742
1729
|
}
|
|
1743
|
-
function
|
|
1744
|
-
|
|
1730
|
+
async function stopConversationTurn(deps, ctx, pluginConfig) {
|
|
1731
|
+
const target = await resolveControlTarget(ctx);
|
|
1732
|
+
if (!target) return "Cannot stop Codex because this command did not include a stable binding identity.";
|
|
1733
|
+
return (await deps.stopCodexConversationTurn({
|
|
1734
|
+
identity: target.identity,
|
|
1735
|
+
bindingStore: deps.bindingStore,
|
|
1736
|
+
pluginConfig,
|
|
1737
|
+
agentDir: target.agentDir,
|
|
1738
|
+
config: ctx.config
|
|
1739
|
+
})).message;
|
|
1745
1740
|
}
|
|
1746
|
-
function
|
|
1747
|
-
|
|
1741
|
+
async function steerConversationTurn(deps, ctx, pluginConfig, message) {
|
|
1742
|
+
const target = await resolveControlTarget(ctx);
|
|
1743
|
+
if (!target) return "Cannot steer Codex because this command did not include a stable binding identity.";
|
|
1744
|
+
return (await deps.steerCodexConversationTurn({
|
|
1745
|
+
identity: target.identity,
|
|
1746
|
+
bindingStore: deps.bindingStore,
|
|
1747
|
+
message,
|
|
1748
|
+
pluginConfig,
|
|
1749
|
+
agentDir: target.agentDir,
|
|
1750
|
+
config: ctx.config
|
|
1751
|
+
})).message;
|
|
1748
1752
|
}
|
|
1749
|
-
function
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
+
async function setConversationModel(deps, ctx, pluginConfig, args) {
|
|
1754
|
+
if (args.length > 1) return "Usage: /codex model <model>";
|
|
1755
|
+
const [model = ""] = args;
|
|
1756
|
+
const normalized = model.trim();
|
|
1757
|
+
if (normalized && isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
|
|
1758
|
+
const target = await resolveControlTarget(ctx);
|
|
1759
|
+
if (!target) return "Cannot set Codex model because this command did not include a stable binding identity.";
|
|
1760
|
+
if (!normalized) {
|
|
1761
|
+
const binding = await deps.bindingStore.read(target.identity);
|
|
1762
|
+
return binding?.model ? `Codex model: ${formatCodexDisplayText(binding.model)}` : "Usage: /codex model <model>";
|
|
1763
|
+
}
|
|
1764
|
+
return await deps.setCodexConversationModel({
|
|
1765
|
+
identity: target.identity,
|
|
1766
|
+
bindingStore: deps.bindingStore,
|
|
1767
|
+
pluginConfig,
|
|
1768
|
+
model: normalized,
|
|
1769
|
+
agentDir: target.agentDir,
|
|
1770
|
+
config: ctx.config
|
|
1771
|
+
});
|
|
1753
1772
|
}
|
|
1754
|
-
function
|
|
1755
|
-
|
|
1773
|
+
async function setConversationFastMode(deps, ctx, args) {
|
|
1774
|
+
if (args.length > 1) return "Usage: /codex fast [on|off|status]";
|
|
1775
|
+
const target = await resolveControlTarget(ctx);
|
|
1776
|
+
if (!target) return "Cannot set Codex fast mode because this command did not include a stable binding identity.";
|
|
1777
|
+
const value = args[0];
|
|
1778
|
+
const parsed = parseCodexFastModeArg(value);
|
|
1779
|
+
if (value && parsed == null && value.trim().toLowerCase() !== "status") return "Usage: /codex fast [on|off|status]";
|
|
1780
|
+
return await deps.setCodexConversationFastMode({
|
|
1781
|
+
identity: target.identity,
|
|
1782
|
+
bindingStore: deps.bindingStore,
|
|
1783
|
+
enabled: parsed
|
|
1784
|
+
});
|
|
1756
1785
|
}
|
|
1757
|
-
function
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
if (
|
|
1761
|
-
|
|
1786
|
+
async function setConversationPermissions(deps, ctx, args) {
|
|
1787
|
+
if (args.length > 1) return "Usage: /codex permissions [default|yolo|status]";
|
|
1788
|
+
const target = await resolveControlTarget(ctx);
|
|
1789
|
+
if (!target) return "Cannot set Codex permissions because this command did not include a stable binding identity.";
|
|
1790
|
+
const value = args[0];
|
|
1791
|
+
const parsed = parseCodexPermissionsModeArg(value);
|
|
1792
|
+
if (value && !parsed && value.trim().toLowerCase() !== "status") return "Usage: /codex permissions [default|yolo|status]";
|
|
1793
|
+
return await deps.setCodexConversationPermissions({
|
|
1794
|
+
identity: target.identity,
|
|
1795
|
+
bindingStore: deps.bindingStore,
|
|
1796
|
+
mode: parsed
|
|
1797
|
+
});
|
|
1762
1798
|
}
|
|
1763
1799
|
async function startThreadAction(deps, ctx, pluginConfig, kind, args) {
|
|
1764
1800
|
const label = kind === "compact" ? "compaction" : "review";
|
|
@@ -1783,273 +1819,248 @@ async function startThreadAction(deps, ctx, pluginConfig, kind, args) {
|
|
|
1783
1819
|
});
|
|
1784
1820
|
return `Started Codex ${label} for thread ${formatCodexDisplayText(binding.threadId)}.`;
|
|
1785
1821
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
current += char;
|
|
1796
|
-
escaping = false;
|
|
1797
|
-
tokenStarted = true;
|
|
1798
|
-
continue;
|
|
1799
|
-
}
|
|
1800
|
-
if (char === "\\" && quote !== "'") {
|
|
1801
|
-
escaping = true;
|
|
1802
|
-
tokenStarted = true;
|
|
1803
|
-
continue;
|
|
1804
|
-
}
|
|
1805
|
-
if (quote) {
|
|
1806
|
-
if (char === quote) quote = void 0;
|
|
1807
|
-
else current += char;
|
|
1808
|
-
tokenStarted = true;
|
|
1809
|
-
continue;
|
|
1810
|
-
}
|
|
1811
|
-
if (char === "\"" || char === "'") {
|
|
1812
|
-
quote = char;
|
|
1813
|
-
tokenStarted = true;
|
|
1814
|
-
continue;
|
|
1815
|
-
}
|
|
1816
|
-
if (/\s/.test(char)) {
|
|
1817
|
-
if (tokenStarted) {
|
|
1818
|
-
args.push(current);
|
|
1819
|
-
current = "";
|
|
1820
|
-
tokenStarted = false;
|
|
1821
|
-
}
|
|
1822
|
-
continue;
|
|
1823
|
-
}
|
|
1824
|
-
current += char;
|
|
1825
|
-
tokenStarted = true;
|
|
1822
|
+
//#endregion
|
|
1823
|
+
//#region extensions/codex/src/command-plugins-management.ts
|
|
1824
|
+
const POLICY_REFRESH_HINT = "New Codex conversations pick this up automatically. Use /new or /reset to refresh the current one.";
|
|
1825
|
+
async function handleCodexPluginsSubcommand(ctx, rest, io) {
|
|
1826
|
+
const [verb = "list", ...args] = rest;
|
|
1827
|
+
const normalized = verb.toLowerCase();
|
|
1828
|
+
if (normalized === "menu") {
|
|
1829
|
+
if (args.length > 0) return { text: "Usage: /codex plugins menu" };
|
|
1830
|
+
return buildPluginsMenuReply();
|
|
1826
1831
|
}
|
|
1827
|
-
if (
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
}
|
|
1831
|
-
function parseBindArgs(args) {
|
|
1832
|
-
const parsed = {};
|
|
1833
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1834
|
-
const arg = expectDefined(args[index], "current Codex bind argument");
|
|
1835
|
-
if (arg === "--help" || arg === "-h") {
|
|
1836
|
-
parsed.help = true;
|
|
1837
|
-
continue;
|
|
1838
|
-
}
|
|
1839
|
-
if (arg === "--cwd") {
|
|
1840
|
-
const value = readRequiredOptionValue(args, index);
|
|
1841
|
-
if (!value || parsed.cwd !== void 0) {
|
|
1842
|
-
parsed.help = true;
|
|
1843
|
-
continue;
|
|
1844
|
-
}
|
|
1845
|
-
parsed.cwd = value;
|
|
1846
|
-
index += 1;
|
|
1847
|
-
continue;
|
|
1848
|
-
}
|
|
1849
|
-
if (arg === "--model") {
|
|
1850
|
-
const value = readRequiredOptionValue(args, index);
|
|
1851
|
-
if (!value || parsed.model !== void 0) {
|
|
1852
|
-
parsed.help = true;
|
|
1853
|
-
continue;
|
|
1854
|
-
}
|
|
1855
|
-
parsed.model = value;
|
|
1856
|
-
index += 1;
|
|
1857
|
-
continue;
|
|
1858
|
-
}
|
|
1859
|
-
if (arg === "--provider" || arg === "--model-provider") {
|
|
1860
|
-
const value = readRequiredOptionValue(args, index);
|
|
1861
|
-
if (!value || parsed.provider !== void 0) {
|
|
1862
|
-
parsed.help = true;
|
|
1863
|
-
continue;
|
|
1864
|
-
}
|
|
1865
|
-
parsed.provider = value;
|
|
1866
|
-
index += 1;
|
|
1867
|
-
continue;
|
|
1868
|
-
}
|
|
1869
|
-
if (!arg.startsWith("-") && !parsed.threadId) {
|
|
1870
|
-
parsed.threadId = arg;
|
|
1871
|
-
continue;
|
|
1872
|
-
}
|
|
1873
|
-
parsed.help = true;
|
|
1832
|
+
if (normalized === "help") {
|
|
1833
|
+
if (args.length > 0) return { text: "Usage: /codex plugins help" };
|
|
1834
|
+
return { text: buildPluginsHelp() };
|
|
1874
1835
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
return parsed;
|
|
1880
|
-
}
|
|
1881
|
-
function parseCodexCliSessionsArgs(args) {
|
|
1882
|
-
const parsed = { filter: "" };
|
|
1883
|
-
const filter = [];
|
|
1884
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1885
|
-
const arg = expectDefined(args[index], "current Codex sessions argument");
|
|
1886
|
-
if (arg === "--help" || arg === "-h") {
|
|
1887
|
-
parsed.help = true;
|
|
1888
|
-
continue;
|
|
1889
|
-
}
|
|
1890
|
-
if (arg === "--host" || arg === "--node") {
|
|
1891
|
-
const value = readRequiredOptionValue(args, index);
|
|
1892
|
-
if (!value || parsed.host !== void 0) {
|
|
1893
|
-
parsed.help = true;
|
|
1894
|
-
continue;
|
|
1895
|
-
}
|
|
1896
|
-
parsed.host = value;
|
|
1897
|
-
index += 1;
|
|
1898
|
-
continue;
|
|
1899
|
-
}
|
|
1900
|
-
if (arg === "--limit") {
|
|
1901
|
-
const parsedLimit = parseStrictPositiveInteger(readRequiredOptionValue(args, index));
|
|
1902
|
-
if (parsedLimit === void 0) {
|
|
1903
|
-
parsed.help = true;
|
|
1904
|
-
continue;
|
|
1905
|
-
}
|
|
1906
|
-
parsed.limit = parsedLimit;
|
|
1907
|
-
index += 1;
|
|
1908
|
-
continue;
|
|
1909
|
-
}
|
|
1910
|
-
if (arg.startsWith("-")) {
|
|
1911
|
-
parsed.help = true;
|
|
1912
|
-
continue;
|
|
1913
|
-
}
|
|
1914
|
-
filter.push(arg);
|
|
1836
|
+
if (normalized === "list") {
|
|
1837
|
+
if (args.length > 0) return { text: "Usage: /codex plugins list" };
|
|
1838
|
+
const current = await io.readConfig();
|
|
1839
|
+
return { text: formatPluginList(current.plugins ?? {}, { globalEnabled: current.enabled === true }) };
|
|
1915
1840
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1841
|
+
const target = args[0];
|
|
1842
|
+
if (normalized === "enable" || normalized === "disable") {
|
|
1843
|
+
if (args.length === 0) return buildPluginNamePickerReply(normalized, await io.readConfig());
|
|
1844
|
+
if (!target || args.length > 1) return { text: `Usage: /codex plugins ${normalized} <name>` };
|
|
1845
|
+
if (!canMutateCodexHost(ctx)) return { text: `Only an owner or operator.admin gateway client can run /codex plugins ${normalized}.` };
|
|
1846
|
+
const wantEnabled = normalized === "enable";
|
|
1847
|
+
if (!((await io.readConfig()).plugins ?? {})[target]) return { text: `Codex sub-plugin '${formatCodexDisplayText(target)}' is not configured. Run '/codex plugins list' to see configured plugins.` };
|
|
1848
|
+
await io.mutate((block) => {
|
|
1849
|
+
if (wantEnabled) block.enabled = true;
|
|
1850
|
+
block.plugins ??= {};
|
|
1851
|
+
block.plugins[target] = {
|
|
1852
|
+
...block.plugins[target],
|
|
1853
|
+
enabled: wantEnabled
|
|
1854
|
+
};
|
|
1855
|
+
});
|
|
1856
|
+
return { text: `${formatCodexDisplayText(target)}: ${wantEnabled ? "enabled" : "disabled"} in openclaw.json. ${POLICY_REFRESH_HINT}` };
|
|
1857
|
+
}
|
|
1858
|
+
return { text: `Unknown /codex plugins subcommand: ${formatCodexDisplayText(verb)}\n\n${buildPluginsHelp()}` };
|
|
1919
1859
|
}
|
|
1920
|
-
function
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1860
|
+
function buildPluginsMenuReply() {
|
|
1861
|
+
return {
|
|
1862
|
+
text: [
|
|
1863
|
+
"Codex sub-plugins. Pick a sub-action or type:",
|
|
1864
|
+
"",
|
|
1865
|
+
" 1. /codex plugins list",
|
|
1866
|
+
" 2. /codex plugins enable",
|
|
1867
|
+
" 3. /codex plugins disable",
|
|
1868
|
+
" 4. /codex plugins help",
|
|
1869
|
+
"",
|
|
1870
|
+
"Type '/codex' to go back to the main menu."
|
|
1871
|
+
].join("\n"),
|
|
1872
|
+
presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", "Pick a Codex sub-plugin action:", [
|
|
1873
|
+
{
|
|
1874
|
+
label: "list",
|
|
1875
|
+
command: "/codex plugins list"
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
label: "enable",
|
|
1879
|
+
command: "/codex plugins enable"
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
label: "disable",
|
|
1883
|
+
command: "/codex plugins disable"
|
|
1884
|
+
},
|
|
1885
|
+
{
|
|
1886
|
+
label: "help",
|
|
1887
|
+
command: "/codex plugins help"
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
label: "back",
|
|
1891
|
+
command: "/codex"
|
|
1942
1892
|
}
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
continue;
|
|
1946
|
-
}
|
|
1947
|
-
if (!arg.startsWith("-") && !parsed.threadId) {
|
|
1948
|
-
parsed.threadId = arg;
|
|
1949
|
-
continue;
|
|
1950
|
-
}
|
|
1951
|
-
parsed.help = true;
|
|
1952
|
-
}
|
|
1953
|
-
parsed.threadId = normalizeOptionalString(parsed.threadId);
|
|
1954
|
-
parsed.host = normalizeOptionalString(parsed.host);
|
|
1955
|
-
return parsed;
|
|
1893
|
+
])
|
|
1894
|
+
};
|
|
1956
1895
|
}
|
|
1957
|
-
function
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1896
|
+
function buildPluginNamePickerReply(verb, current) {
|
|
1897
|
+
const globalEnabled = current.enabled === true;
|
|
1898
|
+
const eligible = Object.entries(current.plugins ?? {}).toSorted(([left], [right]) => left.localeCompare(right)).filter(([, entry]) => {
|
|
1899
|
+
const effectivelyEnabled = globalEnabled && entry.enabled !== false;
|
|
1900
|
+
return verb === "disable" ? effectivelyEnabled : !effectivelyEnabled;
|
|
1901
|
+
});
|
|
1902
|
+
if (eligible.length === 0) return {
|
|
1903
|
+
text: [
|
|
1904
|
+
`No configured ${verb === "enable" ? "disabled" : "enabled"} Codex sub-plugins found.`,
|
|
1905
|
+
"",
|
|
1906
|
+
"Type '/codex plugins list' to inspect configured sub-plugins.",
|
|
1907
|
+
"Type '/codex plugins menu' to go back to the plugins menu."
|
|
1908
|
+
].join("\n"),
|
|
1909
|
+
presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", "Pick another Codex sub-plugin action:", [{
|
|
1910
|
+
label: "list",
|
|
1911
|
+
command: "/codex plugins list"
|
|
1912
|
+
}, {
|
|
1913
|
+
label: "back",
|
|
1914
|
+
command: "/codex plugins menu"
|
|
1915
|
+
}])
|
|
1916
|
+
};
|
|
1917
|
+
const buttons = [...eligible.map(([key]) => ({
|
|
1918
|
+
label: formatCodexDisplayText(key),
|
|
1919
|
+
command: `/codex plugins ${verb} ${key}`
|
|
1920
|
+
})), {
|
|
1921
|
+
label: "back",
|
|
1922
|
+
command: "/codex plugins menu"
|
|
1923
|
+
}];
|
|
1924
|
+
return {
|
|
1925
|
+
text: [
|
|
1926
|
+
`Codex sub-plugins to ${verb}. Pick one or type:`,
|
|
1927
|
+
"",
|
|
1928
|
+
...eligible.map(([key], index) => ` ${index + 1}. /codex plugins ${verb} ${key}`),
|
|
1929
|
+
"",
|
|
1930
|
+
...verb === "enable" && !globalEnabled ? ["Global codexPlugins.enabled is off; enabling one configured sub-plugin turns it on.", ""] : [],
|
|
1931
|
+
"Type '/codex plugins menu' to go back to the plugins menu."
|
|
1932
|
+
].join("\n"),
|
|
1933
|
+
presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", `Pick a Codex sub-plugin to ${verb}:`, buttons)
|
|
1963
1934
|
};
|
|
1964
|
-
let sawAction = false;
|
|
1965
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1966
|
-
const arg = args[index];
|
|
1967
|
-
if (arg === "--help" || arg === "-h") {
|
|
1968
|
-
parsed.help = true;
|
|
1969
|
-
continue;
|
|
1970
|
-
}
|
|
1971
|
-
if (arg === "status" || arg === "install") {
|
|
1972
|
-
if (sawAction) {
|
|
1973
|
-
parsed.help = true;
|
|
1974
|
-
continue;
|
|
1975
|
-
}
|
|
1976
|
-
sawAction = true;
|
|
1977
|
-
parsed.action = arg;
|
|
1978
|
-
continue;
|
|
1979
|
-
}
|
|
1980
|
-
if (arg === "--source" || arg === "--marketplace-source") {
|
|
1981
|
-
const value = readRequiredOptionValue(args, index);
|
|
1982
|
-
if (!value || parsed.overrides.marketplaceSource !== void 0) {
|
|
1983
|
-
parsed.help = true;
|
|
1984
|
-
continue;
|
|
1985
|
-
}
|
|
1986
|
-
parsed.overrides.marketplaceSource = value;
|
|
1987
|
-
index += 1;
|
|
1988
|
-
continue;
|
|
1989
|
-
}
|
|
1990
|
-
if (arg === "--marketplace-path" || arg === "--path") {
|
|
1991
|
-
const value = readRequiredOptionValue(args, index);
|
|
1992
|
-
if (!value || parsed.overrides.marketplacePath !== void 0) {
|
|
1993
|
-
parsed.help = true;
|
|
1994
|
-
continue;
|
|
1995
|
-
}
|
|
1996
|
-
parsed.overrides.marketplacePath = value;
|
|
1997
|
-
index += 1;
|
|
1998
|
-
continue;
|
|
1999
|
-
}
|
|
2000
|
-
if (arg === "--marketplace") {
|
|
2001
|
-
const value = readRequiredOptionValue(args, index);
|
|
2002
|
-
if (!value || parsed.overrides.marketplaceName !== void 0) {
|
|
2003
|
-
parsed.help = true;
|
|
2004
|
-
continue;
|
|
2005
|
-
}
|
|
2006
|
-
parsed.overrides.marketplaceName = value;
|
|
2007
|
-
index += 1;
|
|
2008
|
-
continue;
|
|
2009
|
-
}
|
|
2010
|
-
if (arg === "--plugin" || arg === "--server" || arg === "--mcp-server") {
|
|
2011
|
-
const value = readRequiredOptionValue(args, index);
|
|
2012
|
-
const configKey = arg === "--plugin" ? "pluginName" : "mcpServerName";
|
|
2013
|
-
if (!value || parsed.persistentIdentity[configKey] !== void 0) {
|
|
2014
|
-
parsed.help = true;
|
|
2015
|
-
continue;
|
|
2016
|
-
}
|
|
2017
|
-
parsed.persistentIdentity[configKey] = value.trim();
|
|
2018
|
-
index += 1;
|
|
2019
|
-
continue;
|
|
2020
|
-
}
|
|
2021
|
-
parsed.help = true;
|
|
2022
|
-
}
|
|
2023
|
-
parsed.overrides = normalizeComputerUseStringOverrides(parsed.overrides);
|
|
2024
|
-
parsed.hasOverrides = Object.values(parsed.overrides).some(Boolean);
|
|
2025
|
-
return parsed;
|
|
2026
1935
|
}
|
|
2027
|
-
function
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
].filter((arg) => Boolean(arg));
|
|
2036
|
-
return ["One-off Computer Use plugin/server overrides are no longer supported.", `Set ${settings.join(" and ")} persistently, then rerun ${retryArgs.join(" ")}.`].join(" ");
|
|
1936
|
+
function buildPluginsHelp() {
|
|
1937
|
+
return [
|
|
1938
|
+
"Codex sub-plugin management (writes only to ~/.openclaw/openclaw.json, never to ~/.codex/config.toml):",
|
|
1939
|
+
"- /codex plugins (alias for list)",
|
|
1940
|
+
"- /codex plugins list show all configured Codex sub-plugins",
|
|
1941
|
+
"- /codex plugins enable <name> enable a configured sub-plugin",
|
|
1942
|
+
"- /codex plugins disable <name> disable a configured sub-plugin"
|
|
1943
|
+
].join("\n");
|
|
2037
1944
|
}
|
|
2038
|
-
function
|
|
2039
|
-
const
|
|
2040
|
-
const
|
|
2041
|
-
if (
|
|
2042
|
-
|
|
1945
|
+
function formatPluginList(plugins, options = {}) {
|
|
1946
|
+
const globalEnabled = options.globalEnabled === true;
|
|
1947
|
+
const keys = Object.keys(plugins).toSorted();
|
|
1948
|
+
if (keys.length === 0) return "No Codex sub-plugins configured under plugins.entries.codex.config.codexPlugins.plugins";
|
|
1949
|
+
const rows = keys.map((key) => {
|
|
1950
|
+
const entry = plugins[key] ?? {};
|
|
1951
|
+
const state = globalEnabled && entry.enabled !== false ? "ON " : "OFF";
|
|
1952
|
+
return {
|
|
1953
|
+
displayKey: formatCodexDisplayText(key),
|
|
1954
|
+
state,
|
|
1955
|
+
pluginName: formatCodexDisplayText(entry.pluginName ?? key),
|
|
1956
|
+
marketplace: formatCodexDisplayText(entry.marketplaceName ?? "?")
|
|
1957
|
+
};
|
|
1958
|
+
});
|
|
1959
|
+
const keyW = Math.max(...rows.map((r) => r.displayKey.length));
|
|
1960
|
+
const pluginW = Math.max(...rows.map((r) => r.pluginName.length));
|
|
1961
|
+
return [
|
|
1962
|
+
"Codex sub-plugins in Openclaw config (~/.openclaw/openclaw.json):",
|
|
1963
|
+
"",
|
|
1964
|
+
...rows.map((r) => ` ${r.state} ${r.displayKey.padEnd(keyW)} ${r.pluginName.padEnd(pluginW)} [${r.marketplace}]`),
|
|
1965
|
+
"",
|
|
1966
|
+
...globalEnabled ? [] : ["Global codexPlugins.enabled is off; configured sub-plugins are inactive.", ""],
|
|
1967
|
+
"New Codex conversations pick up policy changes automatically; /new or /reset to refresh the current one."
|
|
1968
|
+
].join("\n");
|
|
2043
1969
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
const
|
|
2049
|
-
if (
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2052
|
-
return
|
|
1970
|
+
//#endregion
|
|
1971
|
+
//#region extensions/codex/src/command-handlers.ts
|
|
1972
|
+
async function handleCodexSubcommand(ctx, options) {
|
|
1973
|
+
const deps = resolveCodexCommandDeps(options.deps);
|
|
1974
|
+
const args = splitArgs(ctx.args);
|
|
1975
|
+
if (args.length === 0) return buildCodexSubcommandPickerReply();
|
|
1976
|
+
const [subcommand = "status", ...rest] = args;
|
|
1977
|
+
const normalized = subcommand.toLowerCase();
|
|
1978
|
+
if (normalized === "help") return { text: buildHelp() };
|
|
1979
|
+
if (CODEX_NATIVE_CONTROL_SUBCOMMANDS.has(normalized) && !returnsBeforeNativeCodexExecution(normalized, rest) && !isReadOnlyCodexGoalCommand(normalized, rest) && !canMutateCodexHost(ctx)) return { text: CODEX_NATIVE_EXECUTION_AUTH_ERROR };
|
|
1980
|
+
const sandboxBlock = resolveCodexNativeCommandSandboxBlock(ctx, normalized, rest);
|
|
1981
|
+
if (sandboxBlock) return { text: sandboxBlock };
|
|
1982
|
+
if (normalized === "plugins") {
|
|
1983
|
+
if (!deps.codexPluginsManagementIo) return { text: "Codex sub-plugin management is not wired up (codexPluginsManagementIo dep is undefined). Edit ~/.openclaw/openclaw.json or use `openclaw config patch` until the runtime exposes the IO." };
|
|
1984
|
+
return await handleCodexPluginsSubcommand(ctx, rest, deps.codexPluginsManagementIo);
|
|
1985
|
+
}
|
|
1986
|
+
if (normalized === "status") {
|
|
1987
|
+
if (rest.length > 0) return { text: "Usage: /codex status" };
|
|
1988
|
+
const { agentDir } = resolveCodexConversationControlScope(ctx);
|
|
1989
|
+
return { text: formatCodexStatus(await deps.readCodexStatusProbes(options.pluginConfig, ctx.config, agentDir)) };
|
|
1990
|
+
}
|
|
1991
|
+
if (normalized === "models") {
|
|
1992
|
+
if (rest.length > 0) return { text: "Usage: /codex models" };
|
|
1993
|
+
const { agentDir } = resolveCodexConversationControlScope(ctx);
|
|
1994
|
+
return { text: formatModels(await deps.listCodexAppServerModels(deps.requestOptions(options.pluginConfig, 100, ctx.config, agentDir))) };
|
|
1995
|
+
}
|
|
1996
|
+
if (normalized === "threads") return { text: await buildThreads(deps, ctx, options.pluginConfig, rest.join(" ")) };
|
|
1997
|
+
if (normalized === "goal") return { text: await handleNativeGoal(deps, ctx, options.pluginConfig, rest) };
|
|
1998
|
+
if (normalized === "sessions") return { text: await buildCodexCliSessions(deps, rest) };
|
|
1999
|
+
if (normalized === "resume") return { text: await resumeThread(deps, ctx, options.pluginConfig, rest) };
|
|
2000
|
+
if (normalized === "bind") return await bindConversation(deps, ctx, options.pluginConfig, rest);
|
|
2001
|
+
if (normalized === "detach" || normalized === "unbind") {
|
|
2002
|
+
if (rest.length > 0) return { text: "Usage: /codex detach" };
|
|
2003
|
+
return { text: await detachConversation(deps, ctx) };
|
|
2004
|
+
}
|
|
2005
|
+
if (normalized === "binding") {
|
|
2006
|
+
if (rest.length > 0) return { text: "Usage: /codex binding" };
|
|
2007
|
+
return { text: await describeConversationBinding(deps, ctx) };
|
|
2008
|
+
}
|
|
2009
|
+
if (normalized === "stop") {
|
|
2010
|
+
if (rest.length > 0) return { text: "Usage: /codex stop" };
|
|
2011
|
+
return { text: await stopConversationTurn(deps, ctx, options.pluginConfig) };
|
|
2012
|
+
}
|
|
2013
|
+
if (normalized === "steer") return { text: await steerConversationTurn(deps, ctx, options.pluginConfig, rest.join(" ")) };
|
|
2014
|
+
if (normalized === "model") return { text: await setConversationModel(deps, ctx, options.pluginConfig, rest) };
|
|
2015
|
+
if (normalized === "fast") {
|
|
2016
|
+
if (isMenuVerb(rest)) return buildCodexFastMenuReply();
|
|
2017
|
+
return { text: await setConversationFastMode(deps, ctx, rest) };
|
|
2018
|
+
}
|
|
2019
|
+
if (normalized === "permissions") {
|
|
2020
|
+
if (isMenuVerb(rest)) return buildCodexPermissionsMenuReply();
|
|
2021
|
+
return { text: await setConversationPermissions(deps, ctx, rest) };
|
|
2022
|
+
}
|
|
2023
|
+
if (normalized === "compact") return { text: await startThreadAction(deps, ctx, options.pluginConfig, "compact", rest) };
|
|
2024
|
+
if (normalized === "review") return { text: await startThreadAction(deps, ctx, options.pluginConfig, "review", rest) };
|
|
2025
|
+
if (normalized === "diagnostics") return await handleCodexDiagnosticsFeedback(deps, ctx, options.pluginConfig, rest.join(" "), "/codex diagnostics");
|
|
2026
|
+
if (normalized === "computer-use" || normalized === "computeruse") {
|
|
2027
|
+
if (isMenuVerb(rest)) return buildCodexComputerUseMenuReply();
|
|
2028
|
+
return { text: await handleComputerUseCommand(deps, ctx, options.pluginConfig, rest) };
|
|
2029
|
+
}
|
|
2030
|
+
if (normalized === "mcp") {
|
|
2031
|
+
if (rest.length > 0) return { text: "Usage: /codex mcp" };
|
|
2032
|
+
const scope = await resolveCommandAppServerScope(deps, ctx, options.pluginConfig);
|
|
2033
|
+
return { text: formatList(await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }, {
|
|
2034
|
+
config: ctx.config,
|
|
2035
|
+
...scope
|
|
2036
|
+
}), "MCP servers") };
|
|
2037
|
+
}
|
|
2038
|
+
if (normalized === "skills") {
|
|
2039
|
+
if (rest.length > 0) return { text: "Usage: /codex skills" };
|
|
2040
|
+
const scope = await resolveCommandAppServerScope(deps, ctx, options.pluginConfig);
|
|
2041
|
+
return { text: formatSkills(await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}, {
|
|
2042
|
+
config: ctx.config,
|
|
2043
|
+
...scope
|
|
2044
|
+
})) };
|
|
2045
|
+
}
|
|
2046
|
+
if (normalized === "account") {
|
|
2047
|
+
if (rest.length > 0) return { text: "Usage: /codex account" };
|
|
2048
|
+
const scope = await resolveCommandAppServerScope(deps, ctx, options.pluginConfig);
|
|
2049
|
+
const requestScope = {
|
|
2050
|
+
config: ctx.config,
|
|
2051
|
+
...scope
|
|
2052
|
+
};
|
|
2053
|
+
const [account, limits] = await Promise.all([deps.safeCodexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, requestScope), deps.safeCodexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, requestScope)]);
|
|
2054
|
+
return { text: formatAccount(account, limits, await readCodexAccountAuthOverview({
|
|
2055
|
+
ctx,
|
|
2056
|
+
agentDir: scope.agentDir,
|
|
2057
|
+
pluginConfig: options.pluginConfig,
|
|
2058
|
+
safeCodexControlRequest: deps.safeCodexControlRequest,
|
|
2059
|
+
account,
|
|
2060
|
+
limits
|
|
2061
|
+
})) };
|
|
2062
|
+
}
|
|
2063
|
+
return { text: `Unknown Codex command: ${formatCodexDisplayText(subcommand)}\n\n${buildHelp()}` };
|
|
2053
2064
|
}
|
|
2054
2065
|
//#endregion
|
|
2055
2066
|
export { handleCodexSubcommand };
|