@openclaw/codex 2026.7.2-beta.3 → 2026.7.2-beta.5
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-DUMF2Xyr.js → app-server-policy-BmDgseo_.js} +1 -1
- package/dist/attempt-client-cleanup-Ia52yRWc.js +127 -0
- package/dist/{attempt-notifications-Ecjyzufe.js → attempt-notifications-CMR2_fmR.js} +1 -1
- package/dist/{media-understanding-provider-IPNNQre6.js → bounded-turn-FEbUXoYT.js} +71 -188
- package/dist/capabilities-BHRSI6Cn.js +36 -0
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-NCTL7A2q.js → command-formatters-ico4ZFnk.js} +3 -37
- package/dist/{command-handlers-DtFbZiba.js → command-handlers-CRmnwI2N.js} +1233 -1221
- package/dist/{compact-Kgy26gIM.js → compact-dEN49UH6.js} +8 -7
- package/dist/{computer-use-C7x4ZaqR.js → computer-use-BjGylO-E.js} +16 -6
- package/dist/{conversation-control-B0s3aIjT.js → conversation-control-DE7d9DOj.js} +6 -5
- package/dist/doctor-contract-api.js +24 -86
- package/dist/{dynamic-tools-D_vSVmv9.js → dynamic-tools-LIt9ojep.js} +135 -2696
- package/dist/event-projector-assistant-message-DTgnPPVF.js +101 -0
- package/dist/event-projector-usage-tvgb1NCX.js +69 -0
- package/dist/harness.js +31 -11
- package/dist/incognito-session-ET_ufDH-.js +2 -0
- package/dist/index.js +250 -275
- package/dist/media-understanding-provider.js +173 -1
- package/dist/{models-DD5zBdX5.js → models-BA01NEez.js} +2 -2
- package/dist/{notification-correlation-C8DVQ_Bq.js → notification-correlation-Bj_Ak_jI.js} +3 -3
- package/dist/{plugin-app-cache-key-DJbhLYSK.js → plugin-app-cache-key-DSXX-rXO.js} +5 -5
- package/dist/{transcript-mirror-D9rTxx2P.js → protocol-validators-Dm4eXZmt.js} +335 -595
- package/dist/{rate-limits-CqhWQ2R9.js → rate-limits-S_PXs0ul.js} +1 -1
- package/dist/realtime-voice-api-BPcA1uN9.js +494 -0
- package/dist/realtime-voice-api.js +2 -0
- package/dist/{request-9yf9pZjf.js → request-CXpPennK.js} +1 -1
- package/dist/{run-attempt-BIoO8ISr.js → run-attempt-C5GTPJOE.js} +3111 -387
- package/dist/{session-binding-Bmt20HfB.js → session-binding-DlVQ-Pxs.js} +957 -890
- package/dist/{session-catalog-CA7R-hX2.js → session-catalog-CP2oCcEb.js} +98 -87
- package/dist/{session-cli-Bxk3-Wqz.js → session-cli-CqZ0Fp2a.js} +2 -2
- package/dist/session-upstream-marker-QE-m5QFG.js +82 -0
- package/dist/settled-turn-finalizer-CA_2QXIE.js +303 -0
- package/dist/{shared-client-CSXLtCLE.js → shared-client-BVKE40c-.js} +1870 -1694
- package/dist/{side-question-DtzquMOb.js → side-question-CO12sHxo.js} +24 -16
- package/dist/{thread-lifecycle-CQ_9mAa1.js → thread-lifecycle-DRg_FdzC.js} +127 -153
- package/dist/transcript-mirror-uKA-A4tB.js +550 -0
- package/dist/upstream-session-fork-_Vrz4WJr.js +320 -0
- package/dist/{usage-CuPpFgTc.js → usage-UpVbdX5G.js} +3 -3
- package/dist/{web-search-provider.runtime-_x5XTeqh.js → web-search-provider.runtime-CaQMYDbp.js} +3 -3
- package/package.json +7 -8
- package/npm-shrinkwrap.json +0 -202
- package/dist/{auth-binding-C1czuHPA.js → auth-binding-B4FfbcCg.js} +1 -1
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { t as listAllCodexAppServerModels } from "./models-
|
|
5
|
-
import {
|
|
6
|
-
import { s as summarizeCodexAccountUsage } from "./rate-limits-
|
|
1
|
+
import { N as resolveCodexAppServerAuthProfileIdForAgent, V as isJsonObject } from "./shared-client-BVKE40c-.js";
|
|
2
|
+
import { O as isCodexFastServiceTier, c as reclaimCurrentCodexSessionGeneration, i as createCodexSessionGenerationSupersededError, l as sessionBindingIdentity, n as assertCodexBindingMayBeReplaced, r as bindingStoreKey, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-DlVQ-Pxs.js";
|
|
3
|
+
import { r as assertCodexThreadResumeResponse } from "./protocol-validators-Dm4eXZmt.js";
|
|
4
|
+
import { t as listAllCodexAppServerModels } from "./models-BA01NEez.js";
|
|
5
|
+
import { t as CODEX_CONTROL_METHODS } from "./capabilities-BHRSI6Cn.js";
|
|
6
|
+
import { s as summarizeCodexAccountUsage } from "./rate-limits-S_PXs0ul.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-ico4ZFnk.js";
|
|
7
8
|
import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-D1Wky__S.js";
|
|
8
|
-
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-
|
|
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-DE7d9DOj.js";
|
|
9
10
|
import { i as readCodexConversationBindingData, n as createCodexCliNodeConversationBindingData, o as resolveCodexDefaultWorkspaceDir, r as createCodexConversationBindingData } from "./conversation-binding-data-BHRpYbX3.js";
|
|
10
|
-
import { a as resolveCodexBindingAppServerConnection } from "./notification-correlation-
|
|
11
|
-
import { c as requestOptions, l as safeCodexControlRequest, o as codexControlRequest, p as formatCodexCliSessions, s as readCodexStatusProbes } from "./session-catalog-
|
|
12
|
-
import { i as readCodexComputerUseStatus, n as installCodexComputerUse } from "./computer-use-
|
|
11
|
+
import { a as resolveCodexBindingAppServerConnection } from "./notification-correlation-Bj_Ak_jI.js";
|
|
12
|
+
import { c as requestOptions, l as safeCodexControlRequest, o as codexControlRequest, p as formatCodexCliSessions, s as readCodexStatusProbes } from "./session-catalog-CP2oCcEb.js";
|
|
13
|
+
import { i as readCodexComputerUseStatus, n as installCodexComputerUse } from "./computer-use-BjGylO-E.js";
|
|
13
14
|
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
14
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
15
15
|
import crypto from "node:crypto";
|
|
16
16
|
import { ensureAuthProfileStore, findNormalizedProviderValue, resolveAgentDir, resolveAuthProfileEligibility, resolveAuthProfileOrder, resolveProfileUnusableUntilForDisplay, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
17
17
|
import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
|
18
18
|
import { normalizeOptionalString, normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
19
19
|
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
20
|
+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
20
21
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
21
22
|
import { MODEL_SELECTION_LOCKED_MESSAGE, isModelSelectionLocked } from "openclaw/plugin-sdk/model-session-runtime";
|
|
22
23
|
//#region extensions/codex/src/command-account.ts
|
|
@@ -349,210 +350,8 @@ function buildCodexCommandPickerPresentation(title, prompt, buttons) {
|
|
|
349
350
|
};
|
|
350
351
|
}
|
|
351
352
|
//#endregion
|
|
352
|
-
//#region extensions/codex/src/command-
|
|
353
|
-
|
|
354
|
-
async function handleCodexPluginsSubcommand(ctx, rest, io) {
|
|
355
|
-
const [verb = "list", ...args] = rest;
|
|
356
|
-
const normalized = verb.toLowerCase();
|
|
357
|
-
if (normalized === "menu") {
|
|
358
|
-
if (args.length > 0) return { text: "Usage: /codex plugins menu" };
|
|
359
|
-
return buildPluginsMenuReply();
|
|
360
|
-
}
|
|
361
|
-
if (normalized === "help") {
|
|
362
|
-
if (args.length > 0) return { text: "Usage: /codex plugins help" };
|
|
363
|
-
return { text: buildPluginsHelp() };
|
|
364
|
-
}
|
|
365
|
-
if (normalized === "list") {
|
|
366
|
-
if (args.length > 0) return { text: "Usage: /codex plugins list" };
|
|
367
|
-
const current = await io.readConfig();
|
|
368
|
-
return { text: formatPluginList(current.plugins ?? {}, { globalEnabled: current.enabled === true }) };
|
|
369
|
-
}
|
|
370
|
-
const target = args[0];
|
|
371
|
-
if (normalized === "enable" || normalized === "disable") {
|
|
372
|
-
if (args.length === 0) return buildPluginNamePickerReply(normalized, await io.readConfig());
|
|
373
|
-
if (!target || args.length > 1) return { text: `Usage: /codex plugins ${normalized} <name>` };
|
|
374
|
-
if (!canMutateCodexHost(ctx)) return { text: `Only an owner or operator.admin gateway client can run /codex plugins ${normalized}.` };
|
|
375
|
-
const wantEnabled = normalized === "enable";
|
|
376
|
-
if (!((await io.readConfig()).plugins ?? {})[target]) return { text: `Codex sub-plugin '${formatCodexDisplayText(target)}' is not configured. Run '/codex plugins list' to see configured plugins.` };
|
|
377
|
-
await io.mutate((block) => {
|
|
378
|
-
if (wantEnabled) block.enabled = true;
|
|
379
|
-
block.plugins ??= {};
|
|
380
|
-
block.plugins[target] = {
|
|
381
|
-
...block.plugins[target],
|
|
382
|
-
enabled: wantEnabled
|
|
383
|
-
};
|
|
384
|
-
});
|
|
385
|
-
return { text: `${formatCodexDisplayText(target)}: ${wantEnabled ? "enabled" : "disabled"} in openclaw.json. ${POLICY_REFRESH_HINT}` };
|
|
386
|
-
}
|
|
387
|
-
return { text: `Unknown /codex plugins subcommand: ${formatCodexDisplayText(verb)}\n\n${buildPluginsHelp()}` };
|
|
388
|
-
}
|
|
389
|
-
function buildPluginsMenuReply() {
|
|
390
|
-
return {
|
|
391
|
-
text: [
|
|
392
|
-
"Codex sub-plugins. Pick a sub-action or type:",
|
|
393
|
-
"",
|
|
394
|
-
" 1. /codex plugins list",
|
|
395
|
-
" 2. /codex plugins enable",
|
|
396
|
-
" 3. /codex plugins disable",
|
|
397
|
-
" 4. /codex plugins help",
|
|
398
|
-
"",
|
|
399
|
-
"Type '/codex' to go back to the main menu."
|
|
400
|
-
].join("\n"),
|
|
401
|
-
presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", "Pick a Codex sub-plugin action:", [
|
|
402
|
-
{
|
|
403
|
-
label: "list",
|
|
404
|
-
command: "/codex plugins list"
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
label: "enable",
|
|
408
|
-
command: "/codex plugins enable"
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
label: "disable",
|
|
412
|
-
command: "/codex plugins disable"
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
label: "help",
|
|
416
|
-
command: "/codex plugins help"
|
|
417
|
-
},
|
|
418
|
-
{
|
|
419
|
-
label: "back",
|
|
420
|
-
command: "/codex"
|
|
421
|
-
}
|
|
422
|
-
])
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
function buildPluginNamePickerReply(verb, current) {
|
|
426
|
-
const globalEnabled = current.enabled === true;
|
|
427
|
-
const eligible = Object.entries(current.plugins ?? {}).toSorted(([left], [right]) => left.localeCompare(right)).filter(([, entry]) => {
|
|
428
|
-
const effectivelyEnabled = globalEnabled && entry.enabled !== false;
|
|
429
|
-
return verb === "disable" ? effectivelyEnabled : !effectivelyEnabled;
|
|
430
|
-
});
|
|
431
|
-
if (eligible.length === 0) return {
|
|
432
|
-
text: [
|
|
433
|
-
`No configured ${verb === "enable" ? "disabled" : "enabled"} Codex sub-plugins found.`,
|
|
434
|
-
"",
|
|
435
|
-
"Type '/codex plugins list' to inspect configured sub-plugins.",
|
|
436
|
-
"Type '/codex plugins menu' to go back to the plugins menu."
|
|
437
|
-
].join("\n"),
|
|
438
|
-
presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", "Pick another Codex sub-plugin action:", [{
|
|
439
|
-
label: "list",
|
|
440
|
-
command: "/codex plugins list"
|
|
441
|
-
}, {
|
|
442
|
-
label: "back",
|
|
443
|
-
command: "/codex plugins menu"
|
|
444
|
-
}])
|
|
445
|
-
};
|
|
446
|
-
const buttons = [...eligible.map(([key]) => ({
|
|
447
|
-
label: formatCodexDisplayText(key),
|
|
448
|
-
command: `/codex plugins ${verb} ${key}`
|
|
449
|
-
})), {
|
|
450
|
-
label: "back",
|
|
451
|
-
command: "/codex plugins menu"
|
|
452
|
-
}];
|
|
453
|
-
return {
|
|
454
|
-
text: [
|
|
455
|
-
`Codex sub-plugins to ${verb}. Pick one or type:`,
|
|
456
|
-
"",
|
|
457
|
-
...eligible.map(([key], index) => ` ${index + 1}. /codex plugins ${verb} ${key}`),
|
|
458
|
-
"",
|
|
459
|
-
...verb === "enable" && !globalEnabled ? ["Global codexPlugins.enabled is off; enabling one configured sub-plugin turns it on.", ""] : [],
|
|
460
|
-
"Type '/codex plugins menu' to go back to the plugins menu."
|
|
461
|
-
].join("\n"),
|
|
462
|
-
presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", `Pick a Codex sub-plugin to ${verb}:`, buttons)
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
function buildPluginsHelp() {
|
|
466
|
-
return [
|
|
467
|
-
"Codex sub-plugin management (writes only to ~/.openclaw/openclaw.json, never to ~/.codex/config.toml):",
|
|
468
|
-
"- /codex plugins (alias for list)",
|
|
469
|
-
"- /codex plugins list show all configured Codex sub-plugins",
|
|
470
|
-
"- /codex plugins enable <name> enable a configured sub-plugin",
|
|
471
|
-
"- /codex plugins disable <name> disable a configured sub-plugin"
|
|
472
|
-
].join("\n");
|
|
473
|
-
}
|
|
474
|
-
function formatPluginList(plugins, options = {}) {
|
|
475
|
-
const globalEnabled = options.globalEnabled === true;
|
|
476
|
-
const keys = Object.keys(plugins).toSorted();
|
|
477
|
-
if (keys.length === 0) return "No Codex sub-plugins configured under plugins.entries.codex.config.codexPlugins.plugins";
|
|
478
|
-
const rows = keys.map((key) => {
|
|
479
|
-
const entry = plugins[key] ?? {};
|
|
480
|
-
const state = globalEnabled && entry.enabled !== false ? "ON " : "OFF";
|
|
481
|
-
return {
|
|
482
|
-
displayKey: formatCodexDisplayText(key),
|
|
483
|
-
state,
|
|
484
|
-
pluginName: formatCodexDisplayText(entry.pluginName ?? key),
|
|
485
|
-
marketplace: formatCodexDisplayText(entry.marketplaceName ?? "?")
|
|
486
|
-
};
|
|
487
|
-
});
|
|
488
|
-
const keyW = Math.max(...rows.map((r) => r.displayKey.length));
|
|
489
|
-
const pluginW = Math.max(...rows.map((r) => r.pluginName.length));
|
|
490
|
-
return [
|
|
491
|
-
"Codex sub-plugins in Openclaw config (~/.openclaw/openclaw.json):",
|
|
492
|
-
"",
|
|
493
|
-
...rows.map((r) => ` ${r.state} ${r.displayKey.padEnd(keyW)} ${r.pluginName.padEnd(pluginW)} [${r.marketplace}]`),
|
|
494
|
-
"",
|
|
495
|
-
...globalEnabled ? [] : ["Global codexPlugins.enabled is off; configured sub-plugins are inactive.", ""],
|
|
496
|
-
"New Codex conversations pick up policy changes automatically; /new or /reset to refresh the current one."
|
|
497
|
-
].join("\n");
|
|
498
|
-
}
|
|
499
|
-
//#endregion
|
|
500
|
-
//#region extensions/codex/src/command-handlers.ts
|
|
501
|
-
const defaultCodexCommandDeps = {
|
|
502
|
-
codexControlRequest,
|
|
503
|
-
listCodexAppServerModels: listAllCodexAppServerModels,
|
|
504
|
-
readCodexStatusProbes,
|
|
505
|
-
requestOptions,
|
|
506
|
-
safeCodexControlRequest,
|
|
507
|
-
readCodexComputerUseStatus,
|
|
508
|
-
installCodexComputerUse,
|
|
509
|
-
resolveCodexDefaultWorkspaceDir,
|
|
510
|
-
readCodexConversationActiveTurn,
|
|
511
|
-
setCodexConversationFastMode,
|
|
512
|
-
setCodexConversationModel,
|
|
513
|
-
setCodexConversationPermissions,
|
|
514
|
-
steerCodexConversationTurn,
|
|
515
|
-
stopCodexConversationTurn,
|
|
516
|
-
listCodexCliSessionsOnNode: async () => {
|
|
517
|
-
throw new Error("Codex CLI node sessions require Gateway node runtime.");
|
|
518
|
-
},
|
|
519
|
-
resolveCodexCliSessionForBindingOnNode: async () => {
|
|
520
|
-
throw new Error("Codex CLI node sessions require Gateway node runtime.");
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
const CODEX_DIAGNOSTICS_SOURCE = "openclaw-diagnostics";
|
|
524
|
-
const CODEX_DIAGNOSTICS_REASON_MAX_CHARS = 2048;
|
|
525
|
-
const CODEX_DIAGNOSTICS_COOLDOWN_MS = 6e4;
|
|
526
|
-
const CODEX_DIAGNOSTICS_ERROR_MAX_CHARS = 500;
|
|
527
|
-
const CODEX_DIAGNOSTICS_COOLDOWN_MAX_THREADS = 100;
|
|
528
|
-
const CODEX_DIAGNOSTICS_COOLDOWN_MAX_SCOPES = 100;
|
|
529
|
-
const CODEX_DIAGNOSTICS_CONFIRMATION_TTL_MS = 5 * 6e4;
|
|
530
|
-
const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_REQUESTS_PER_SCOPE = 100;
|
|
531
|
-
const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_SCOPES = 100;
|
|
532
|
-
const CODEX_DIAGNOSTICS_SCOPE_FIELD_MAX_CHARS = 128;
|
|
533
|
-
const CODEX_RESUME_SAFE_THREAD_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;
|
|
534
|
-
const CODEX_NATIVE_EXECUTION_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
535
|
-
"bind",
|
|
536
|
-
"resume",
|
|
537
|
-
"steer",
|
|
538
|
-
"model",
|
|
539
|
-
"fast",
|
|
540
|
-
"permissions",
|
|
541
|
-
"compact",
|
|
542
|
-
"review",
|
|
543
|
-
"goal"
|
|
544
|
-
]);
|
|
545
|
-
const CODEX_NATIVE_CONTROL_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
546
|
-
...CODEX_NATIVE_EXECUTION_SUBCOMMANDS,
|
|
547
|
-
"detach",
|
|
548
|
-
"unbind",
|
|
549
|
-
"stop"
|
|
550
|
-
]);
|
|
551
|
-
const { lastUploadByThread: lastCodexDiagnosticsUploadByThread, lastUploadByScope: lastCodexDiagnosticsUploadByScope, pendingConfirmations: pendingCodexDiagnosticsConfirmations, pendingTokensByScope: pendingCodexDiagnosticsConfirmationTokensByScope } = codexDiagnosticsFeedbackState;
|
|
552
|
-
/**
|
|
553
|
-
* No-arg `/codex` picker. Codex owns the command tree; channels render the
|
|
554
|
-
* portable command actions as inline controls when their transport can.
|
|
555
|
-
*/
|
|
353
|
+
//#region extensions/codex/src/command-handler-args.ts
|
|
354
|
+
/** No-arg `/codex` picker. */
|
|
556
355
|
function buildCodexSubcommandPickerReply() {
|
|
557
356
|
const verbs = [
|
|
558
357
|
{
|
|
@@ -595,7 +394,6 @@ function buildCodexSubcommandPickerReply() {
|
|
|
595
394
|
presentation: buildCodexCommandPickerPresentation("Codex commands", "Pick a Codex subcommand:", verbs)
|
|
596
395
|
};
|
|
597
396
|
}
|
|
598
|
-
/** Sub-picker for `/codex fast menu` (on / off / status). */
|
|
599
397
|
function buildCodexFastMenuReply() {
|
|
600
398
|
const modes = [
|
|
601
399
|
"on",
|
|
@@ -620,7 +418,6 @@ function buildCodexFastMenuReply() {
|
|
|
620
418
|
presentation: buildCodexCommandPickerPresentation("Codex fast mode", "Pick a Codex fast mode:", buttons)
|
|
621
419
|
};
|
|
622
420
|
}
|
|
623
|
-
/** Sub-picker for `/codex permissions menu` (default / yolo / status). */
|
|
624
421
|
function buildCodexPermissionsMenuReply() {
|
|
625
422
|
const modes = [
|
|
626
423
|
"default",
|
|
@@ -645,7 +442,6 @@ function buildCodexPermissionsMenuReply() {
|
|
|
645
442
|
presentation: buildCodexCommandPickerPresentation("Codex permissions", "Pick a Codex permissions mode:", buttons)
|
|
646
443
|
};
|
|
647
444
|
}
|
|
648
|
-
/** Sub-picker for `/codex computer-use menu` (status / install). */
|
|
649
445
|
function buildCodexComputerUseMenuReply() {
|
|
650
446
|
const actions = ["status", "install"];
|
|
651
447
|
const buttons = [...actions.map((action) => ({
|
|
@@ -668,503 +464,595 @@ function buildCodexComputerUseMenuReply() {
|
|
|
668
464
|
presentation: buildCodexCommandPickerPresentation("Codex computer-use", "Pick a Codex computer-use action:", buttons)
|
|
669
465
|
};
|
|
670
466
|
}
|
|
671
|
-
/** Returns true when the rest-args are exactly `["menu"]` (case-insensitive). */
|
|
672
467
|
function isMenuVerb(rest) {
|
|
673
468
|
return rest.length === 1 && (rest[0] ?? "").trim().toLowerCase() === "menu";
|
|
674
469
|
}
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
if (
|
|
690
|
-
|
|
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;
|
|
691
510
|
}
|
|
692
|
-
if (
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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;
|
|
724
558
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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);
|
|
728
599
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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;
|
|
735
636
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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;
|
|
743
706
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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);
|
|
751
785
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
...scope
|
|
758
|
-
};
|
|
759
|
-
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)]);
|
|
760
|
-
return { text: formatAccount(account, limits, await readCodexAccountAuthOverview({
|
|
761
|
-
ctx,
|
|
762
|
-
agentDir: scope.agentDir,
|
|
763
|
-
pluginConfig: options.pluginConfig,
|
|
764
|
-
safeCodexControlRequest: deps.safeCodexControlRequest,
|
|
765
|
-
account,
|
|
766
|
-
limits
|
|
767
|
-
})) };
|
|
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);
|
|
768
791
|
}
|
|
769
|
-
|
|
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;
|
|
770
811
|
}
|
|
771
|
-
function
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}));
|
|
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
|
+
};
|
|
780
820
|
}
|
|
781
|
-
function
|
|
782
|
-
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
+
};
|
|
798
844
|
}
|
|
799
|
-
function
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
return action === "status" || action === "get";
|
|
845
|
+
function readPendingCodexDiagnosticsConfirmation(token, now) {
|
|
846
|
+
prunePendingCodexDiagnosticsConfirmations(now);
|
|
847
|
+
return pendingCodexDiagnosticsConfirmations.get(token);
|
|
803
848
|
}
|
|
804
|
-
function
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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)}`));
|
|
818
892
|
}
|
|
893
|
+
return lines.join("\n");
|
|
819
894
|
}
|
|
820
|
-
function
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
+
});
|
|
824
900
|
}
|
|
825
|
-
function
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
if (
|
|
829
|
-
if (
|
|
830
|
-
|
|
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;
|
|
831
909
|
}
|
|
832
|
-
|
|
833
|
-
const
|
|
834
|
-
if (
|
|
835
|
-
|
|
836
|
-
if (
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
pluginConfig,
|
|
840
|
-
config: ctx.config,
|
|
841
|
-
agentDir,
|
|
842
|
-
forceEnable: parsed.action === "install" || parsed.hasOverrides,
|
|
843
|
-
...Object.keys(parsed.overrides).length > 0 ? { overrides: parsed.overrides } : {}
|
|
844
|
-
};
|
|
845
|
-
if (parsed.action === "install") return formatComputerUseStatus(await deps.installCodexComputerUse(params));
|
|
846
|
-
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(", ")}`;
|
|
847
917
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const sessionSource = sessionOwner && existingBinding ? {
|
|
867
|
-
agentId: sessionOwner.agentId,
|
|
868
|
-
sessionId: sessionOwner.sessionId,
|
|
869
|
-
threadId: existingBinding.threadId,
|
|
870
|
-
...sessionOwner.sessionKey ? { sessionKey: sessionOwner.sessionKey } : {}
|
|
871
|
-
} : void 0;
|
|
872
|
-
const authProfileId = existingBinding?.authProfileId;
|
|
873
|
-
const data = createCodexConversationBindingData({
|
|
874
|
-
bindingId,
|
|
875
|
-
workspaceDir,
|
|
876
|
-
agentId: scope.agentId,
|
|
877
|
-
agentDir: scope.agentDir,
|
|
878
|
-
source: currentConversationData?.kind === "codex-app-server-session" ? currentConversationData.source : sessionSource,
|
|
879
|
-
start: {
|
|
880
|
-
id: crypto.randomUUID(),
|
|
881
|
-
threadId: parsed.threadId,
|
|
882
|
-
model: parsed.model,
|
|
883
|
-
modelProvider: parsed.provider,
|
|
884
|
-
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.`;
|
|
885
936
|
}
|
|
886
|
-
}
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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");
|
|
896
958
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
const detached = await ctx.detachConversationBinding();
|
|
902
|
-
if (data?.kind === "codex-app-server-session") await deps.bindingStore.mutate(conversationBindingIdentity(data.bindingId), { kind: "clear" });
|
|
903
|
-
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;
|
|
904
963
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
const data = readCodexConversationBindingData(current);
|
|
908
|
-
if (!current || !data) return "No Codex conversation binding is attached.";
|
|
909
|
-
if (data.kind === "codex-cli-node-session") return [
|
|
910
|
-
"Codex conversation binding:",
|
|
911
|
-
"- Mode: Codex CLI node session",
|
|
912
|
-
`- Node: ${formatCodexDisplayText(data.nodeId)}`,
|
|
913
|
-
`- Session: ${formatCodexDisplayText(data.sessionId)}`,
|
|
914
|
-
`- Workspace: ${formatCodexDisplayText(data.cwd ?? "unknown")}`,
|
|
915
|
-
"- Active run: not tracked"
|
|
916
|
-
].join("\n");
|
|
917
|
-
const identity = conversationBindingIdentity(data.bindingId);
|
|
918
|
-
const threadBinding = await deps.bindingStore.read(identity);
|
|
919
|
-
const active = deps.readCodexConversationActiveTurn(identity);
|
|
920
|
-
return [
|
|
921
|
-
"Codex conversation binding:",
|
|
922
|
-
`- Thread: ${formatCodexDisplayText(threadBinding?.threadId ?? "unknown")}`,
|
|
923
|
-
`- Workspace: ${formatCodexDisplayText(data.workspaceDir)}`,
|
|
924
|
-
`- Model: ${formatCodexDisplayText(threadBinding?.model ?? "default")}`,
|
|
925
|
-
`- Fast: ${isCodexFastServiceTier(threadBinding?.serviceTier) ? "on" : "off"}`,
|
|
926
|
-
`- Permissions: ${threadBinding ? formatPermissionsMode(threadBinding) : "default"}`,
|
|
927
|
-
`- Active run: ${formatCodexDisplayText(active ? active.turnId : "none")}`,
|
|
928
|
-
`- Binding: ${formatCodexDisplayText(data.bindingId)}`
|
|
929
|
-
].join("\n");
|
|
964
|
+
function addTag(tags, key, value) {
|
|
965
|
+
if (typeof value === "string" && value.trim()) tags[key] = value.trim();
|
|
930
966
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
return formatThreads(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listThreads, {
|
|
934
|
-
limit: 10,
|
|
935
|
-
...filter.trim() ? { searchTerm: filter.trim() } : {}
|
|
936
|
-
}, {
|
|
937
|
-
config: ctx.config,
|
|
938
|
-
...scope
|
|
939
|
-
}));
|
|
967
|
+
function formatCodexThreadIdForDisplay(threadId) {
|
|
968
|
+
return escapeCodexChatText(formatCodexTextForDisplay(threadId));
|
|
940
969
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const objective = args.slice(1).join(" ").trim();
|
|
944
|
-
const target = await resolveControlTarget(ctx);
|
|
945
|
-
if (!target) return "Cannot manage the Codex goal because this command has no stable binding identity.";
|
|
946
|
-
const binding = await deps.bindingStore.read(target.identity);
|
|
947
|
-
if (!binding?.threadId) return "No Codex thread is attached to this OpenClaw session yet.";
|
|
948
|
-
const connection = resolveCodexBindingAppServerConnection({
|
|
949
|
-
binding,
|
|
950
|
-
authProfileId: binding.authProfileId,
|
|
951
|
-
pluginConfig
|
|
952
|
-
});
|
|
953
|
-
const goalRequestOptions = {
|
|
954
|
-
agentDir: target.agentDir,
|
|
955
|
-
authProfileId: connection.clientAuthProfileId,
|
|
956
|
-
config: ctx.config,
|
|
957
|
-
...connection.usesSupervisionConnection ? { startOptions: connection.appServer.start } : {}
|
|
958
|
-
};
|
|
959
|
-
if (action === "status" || action === "get") {
|
|
960
|
-
if (args.length > 1) return "Usage: /codex goal [status]";
|
|
961
|
-
return formatNativeGoal(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.getThreadGoal, { threadId: binding.threadId }, goalRequestOptions));
|
|
962
|
-
}
|
|
963
|
-
if (action === "clear") {
|
|
964
|
-
if (args.length > 1) return "Usage: /codex goal clear";
|
|
965
|
-
const response = await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.clearThreadGoal, { threadId: binding.threadId }, goalRequestOptions);
|
|
966
|
-
return isJsonObject(response) && response.cleared === true ? "Cleared the Codex goal." : "No Codex goal was active.";
|
|
967
|
-
}
|
|
968
|
-
const requestedStatus = action === "pause" ? "paused" : action === "resume" ? "active" : action === "block" ? "blocked" : action === "complete" ? "complete" : void 0;
|
|
969
|
-
const isObjectiveUpdate = action === "set";
|
|
970
|
-
if (!requestedStatus && !isObjectiveUpdate || isObjectiveUpdate && !objective) return "Usage: /codex goal [status|set <objective>|pause|resume|block|complete|clear]";
|
|
971
|
-
if (requestedStatus && args.length > 1) return `Usage: /codex goal ${action}`;
|
|
972
|
-
return formatNativeGoal(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.setThreadGoal, {
|
|
973
|
-
threadId: binding.threadId,
|
|
974
|
-
...objective ? { objective } : {},
|
|
975
|
-
...requestedStatus ? { status: requestedStatus } : {}
|
|
976
|
-
}, goalRequestOptions));
|
|
970
|
+
function formatCodexValueForDisplay(value) {
|
|
971
|
+
return escapeCodexChatText(formatCodexTextForDisplay(value));
|
|
977
972
|
}
|
|
978
|
-
function
|
|
979
|
-
const
|
|
980
|
-
if (
|
|
981
|
-
|
|
982
|
-
const status = readString$1(goal, "status") ?? "unknown";
|
|
983
|
-
const tokensUsed = typeof goal.tokensUsed === "number" ? goal.tokensUsed : 0;
|
|
984
|
-
const tokenBudget = typeof goal.tokenBudget === "number" ? goal.tokenBudget : void 0;
|
|
985
|
-
return [
|
|
986
|
-
`Codex goal: ${formatCodexDisplayText(objective)}`,
|
|
987
|
-
`- Status: ${formatCodexDisplayText(status)}`,
|
|
988
|
-
`- Tokens: ${tokensUsed}${tokenBudget === void 0 ? "" : ` / ${tokenBudget}`}`
|
|
989
|
-
].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);
|
|
990
977
|
}
|
|
991
|
-
|
|
992
|
-
const
|
|
993
|
-
if (
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
limit: parsed.limit
|
|
998
|
-
}));
|
|
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;
|
|
999
984
|
}
|
|
1000
|
-
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
if (
|
|
1005
|
-
|
|
1006
|
-
if (isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
|
|
1007
|
-
if (!ctx.sessionId) return "Cannot attach a Codex thread because this command did not include an OpenClaw session id.";
|
|
1008
|
-
const scope = resolveCodexConversationControlScope(ctx);
|
|
1009
|
-
const identity = sessionBindingIdentity({
|
|
1010
|
-
sessionId: ctx.sessionId,
|
|
1011
|
-
sessionKey: ctx.sessionKey,
|
|
1012
|
-
agentId: scope.agentId,
|
|
1013
|
-
config: ctx.config
|
|
1014
|
-
});
|
|
1015
|
-
return await deps.bindingStore.withLease(identity, async () => {
|
|
1016
|
-
if (!await reclaimCurrentCodexSessionGeneration({
|
|
1017
|
-
bindingStore: deps.bindingStore,
|
|
1018
|
-
identity,
|
|
1019
|
-
config: ctx.config
|
|
1020
|
-
})) throw new Error(`Codex session generation is no longer current: ${identity.sessionId}`);
|
|
1021
|
-
const currentBinding = await deps.bindingStore.read(identity);
|
|
1022
|
-
assertCodexBindingMayBeReplaced(currentBinding, "attaching a different resumed thread");
|
|
1023
|
-
const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
|
|
1024
|
-
authProfileId: currentBinding?.authProfileId,
|
|
1025
|
-
agentDir: scope.agentDir,
|
|
1026
|
-
config: ctx.config
|
|
1027
|
-
});
|
|
1028
|
-
const response = assertCodexThreadResumeResponse(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.resumeThread, {
|
|
1029
|
-
threadId: normalizedThreadId,
|
|
1030
|
-
excludeTurns: true
|
|
1031
|
-
}, {
|
|
1032
|
-
config: ctx.config,
|
|
1033
|
-
agentDir: scope.agentDir,
|
|
1034
|
-
authProfileId,
|
|
1035
|
-
sessionKey: ctx.sessionKey,
|
|
1036
|
-
sessionId: ctx.sessionId
|
|
1037
|
-
}));
|
|
1038
|
-
const effectiveThreadId = response.thread.id;
|
|
1039
|
-
if (effectiveThreadId !== normalizedThreadId) throw new Error(`Codex thread/resume returned ${effectiveThreadId} for ${normalizedThreadId}`);
|
|
1040
|
-
const resumedCwd = response.thread.cwd;
|
|
1041
|
-
if (typeof resumedCwd !== "string") throw new Error(`Codex thread/resume returned no cwd for ${normalizedThreadId}`);
|
|
1042
|
-
const modelProvider = normalizeCodexAppServerBindingModelProvider({
|
|
1043
|
-
authProfileId,
|
|
1044
|
-
modelProvider: response.modelProvider ?? void 0,
|
|
1045
|
-
agentDir: scope.agentDir,
|
|
1046
|
-
config: ctx.config
|
|
1047
|
-
});
|
|
1048
|
-
assertCodexBindingMayBeReplaced(await deps.bindingStore.read(identity), "committing a different resumed thread");
|
|
1049
|
-
if (!await deps.bindingStore.mutate(identity, {
|
|
1050
|
-
kind: "set",
|
|
1051
|
-
binding: {
|
|
1052
|
-
threadId: effectiveThreadId,
|
|
1053
|
-
cwd: resumedCwd,
|
|
1054
|
-
authProfileId,
|
|
1055
|
-
model: response.model,
|
|
1056
|
-
modelProvider,
|
|
1057
|
-
historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
|
|
1058
|
-
}
|
|
1059
|
-
})) throw new Error("Codex thread binding changed while attaching the resumed thread.");
|
|
1060
|
-
return `Attached this OpenClaw session to Codex thread ${formatCodexDisplayText(effectiveThreadId)}.`;
|
|
1061
|
-
});
|
|
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;
|
|
1062
991
|
}
|
|
1063
|
-
|
|
1064
|
-
if (!
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
assertCodexBindingMayBeReplaced(await deps.bindingStore.read(sessionBindingIdentity({
|
|
1069
|
-
sessionId: ctx.sessionId,
|
|
1070
|
-
sessionKey: ctx.sessionKey,
|
|
1071
|
-
agentId: scope.agentId,
|
|
1072
|
-
config: ctx.config
|
|
1073
|
-
})), "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);
|
|
1074
997
|
}
|
|
1075
|
-
|
|
1076
|
-
requestedNode: parsed.host,
|
|
1077
|
-
sessionId: parsed.threadId
|
|
1078
|
-
});
|
|
1079
|
-
if (!resolved.session) return `No Codex CLI session ${formatCodexDisplayText(parsed.threadId)} was found on ${formatCodexDisplayText(parsed.host)}.`;
|
|
1080
|
-
const nodeId = resolved.node.nodeId;
|
|
1081
|
-
if (!nodeId) return "Cannot bind Codex CLI session because the selected node did not include a node id.";
|
|
1082
|
-
const scope = resolveCodexConversationControlScope(ctx);
|
|
1083
|
-
const data = createCodexCliNodeConversationBindingData({
|
|
1084
|
-
nodeId,
|
|
1085
|
-
sessionId: parsed.threadId,
|
|
1086
|
-
agentId: scope.agentId,
|
|
1087
|
-
cwd: resolved.session?.cwd
|
|
1088
|
-
});
|
|
1089
|
-
const summary = `Codex CLI session ${formatCodexDisplayText(parsed.threadId)} on ${formatCodexDisplayText(nodeId)}`;
|
|
1090
|
-
const request = await ctx.requestConversationBinding({
|
|
1091
|
-
summary,
|
|
1092
|
-
detachHint: "/codex detach",
|
|
1093
|
-
data
|
|
1094
|
-
});
|
|
1095
|
-
if (request.status === "bound") return `Bound this conversation to Codex CLI session ${formatCodexDisplayText(parsed.threadId)} on ${formatCodexDisplayText(nodeId)}.`;
|
|
1096
|
-
if (request.status === "pending") return request.reply.text ?? "Codex CLI session binding is pending approval.";
|
|
1097
|
-
return formatCodexDisplayText(request.message);
|
|
998
|
+
map.set(key, now);
|
|
1098
999
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
return (await deps.stopCodexConversationTurn({
|
|
1103
|
-
identity: target.identity,
|
|
1104
|
-
bindingStore: deps.bindingStore,
|
|
1105
|
-
pluginConfig,
|
|
1106
|
-
agentDir: target.agentDir,
|
|
1107
|
-
config: ctx.config
|
|
1108
|
-
})).message;
|
|
1000
|
+
function pruneCodexDiagnosticsCooldowns(now) {
|
|
1001
|
+
pruneCodexDiagnosticsCooldownMap(lastCodexDiagnosticsUploadByThread, now);
|
|
1002
|
+
pruneCodexDiagnosticsCooldownMap(lastCodexDiagnosticsUploadByScope, now);
|
|
1109
1003
|
}
|
|
1110
|
-
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
})).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";
|
|
1009
|
+
}
|
|
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}\``;
|
|
1121
1014
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
const [model = ""] = args;
|
|
1125
|
-
const normalized = model.trim();
|
|
1126
|
-
if (normalized && isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
|
|
1127
|
-
const target = await resolveControlTarget(ctx);
|
|
1128
|
-
if (!target) return "Cannot set Codex model because this command did not include a stable binding identity.";
|
|
1129
|
-
if (!normalized) {
|
|
1130
|
-
const binding = await deps.bindingStore.read(target.identity);
|
|
1131
|
-
return binding?.model ? `Codex model: ${formatCodexDisplayText(binding.model)}` : "Usage: /codex model <model>";
|
|
1132
|
-
}
|
|
1133
|
-
return await deps.setCodexConversationModel({
|
|
1134
|
-
identity: target.identity,
|
|
1135
|
-
bindingStore: deps.bindingStore,
|
|
1136
|
-
pluginConfig,
|
|
1137
|
-
model: normalized,
|
|
1138
|
-
agentDir: target.agentDir,
|
|
1139
|
-
config: ctx.config
|
|
1140
|
-
});
|
|
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;
|
|
1141
1017
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
if (
|
|
1146
|
-
|
|
1147
|
-
const parsed = parseCodexFastModeArg(value);
|
|
1148
|
-
if (value && parsed == null && value.trim().toLowerCase() !== "status") return "Usage: /codex fast [on|off|status]";
|
|
1149
|
-
return await deps.setCodexConversationFastMode({
|
|
1150
|
-
identity: target.identity,
|
|
1151
|
-
bindingStore: deps.bindingStore,
|
|
1152
|
-
enabled: parsed
|
|
1153
|
-
});
|
|
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")}`;
|
|
1154
1023
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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
|
+
};
|
|
1167
1053
|
}
|
|
1054
|
+
//#endregion
|
|
1055
|
+
//#region extensions/codex/src/command-handler-scope.ts
|
|
1168
1056
|
async function resolveControlTarget(ctx) {
|
|
1169
1057
|
const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
|
|
1170
1058
|
const scope = resolveCodexConversationControlScope(ctx);
|
|
@@ -1225,6 +1113,8 @@ function resolveCodexConversationControlScope(ctx) {
|
|
|
1225
1113
|
agentDir: resolveAgentDir(ctx.config, sessionAgentId)
|
|
1226
1114
|
};
|
|
1227
1115
|
}
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region extensions/codex/src/command-diagnostics.ts
|
|
1228
1118
|
async function handleCodexDiagnosticsFeedback(deps, ctx, pluginConfig, args, commandPrefix) {
|
|
1229
1119
|
if (ctx.senderIsOwner !== true) return { text: "Only an owner can send Codex diagnostics." };
|
|
1230
1120
|
const parsed = parseDiagnosticsArgs(args);
|
|
@@ -1447,317 +1337,464 @@ async function resolveCodexDiagnosticsTargets(deps, ctx) {
|
|
|
1447
1337
|
targets.push(resolveCodexDiagnosticsTarget(candidate, binding, ctx.config));
|
|
1448
1338
|
}
|
|
1449
1339
|
return targets;
|
|
1450
|
-
}
|
|
1451
|
-
async function resolvePendingCodexDiagnosticsTargets(deps, targets, config) {
|
|
1452
|
-
const resolved = [];
|
|
1453
|
-
for (const target of targets) {
|
|
1454
|
-
const binding = await deps.bindingStore.read(target.identity);
|
|
1455
|
-
if (!binding?.threadId) continue;
|
|
1456
|
-
resolved.push(resolveCodexDiagnosticsTarget(target, binding, config));
|
|
1457
|
-
}
|
|
1458
|
-
return resolved;
|
|
1459
|
-
}
|
|
1460
|
-
function resolveCodexDiagnosticsTarget(target, binding, config) {
|
|
1461
|
-
const candidate = {
|
|
1462
|
-
identity: target.identity,
|
|
1463
|
-
agentDir: target.agentDir,
|
|
1464
|
-
sessionKey: target.sessionKey,
|
|
1465
|
-
sessionId: target.sessionId,
|
|
1466
|
-
channel: target.channel,
|
|
1467
|
-
channelId: target.channelId,
|
|
1468
|
-
accountId: target.accountId,
|
|
1469
|
-
messageThreadId: target.messageThreadId,
|
|
1470
|
-
threadParentId: target.threadParentId
|
|
1471
|
-
};
|
|
1472
|
-
if (binding.connectionScope === "supervision") return {
|
|
1473
|
-
...candidate,
|
|
1474
|
-
threadId: binding.threadId,
|
|
1475
|
-
connectionScope: binding.connectionScope,
|
|
1476
|
-
appServerRuntimeFingerprint: binding.appServerRuntimeFingerprint,
|
|
1477
|
-
pendingSupervisionBranch: binding.pendingSupervisionBranch
|
|
1478
|
-
};
|
|
1479
|
-
const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
|
|
1480
|
-
authProfileId: binding.authProfileId,
|
|
1481
|
-
agentDir: target.agentDir,
|
|
1482
|
-
config
|
|
1483
|
-
});
|
|
1484
|
-
return {
|
|
1485
|
-
...candidate,
|
|
1486
|
-
threadId: binding.threadId,
|
|
1487
|
-
authProfileId
|
|
1488
|
-
};
|
|
1489
|
-
}
|
|
1490
|
-
function codexDiagnosticsTargetsMatch(expected, actual) {
|
|
1491
|
-
const fingerprint = (target) => JSON.stringify([
|
|
1492
|
-
bindingStoreKey(target.identity),
|
|
1493
|
-
target.threadId,
|
|
1494
|
-
target.connectionScope ?? null,
|
|
1495
|
-
target.pendingSupervisionBranch?.connectionFingerprint ?? target.appServerRuntimeFingerprint ?? null,
|
|
1496
|
-
target.authProfileId ?? null
|
|
1497
|
-
]);
|
|
1498
|
-
const expectedTargets = expected.map(fingerprint).toSorted();
|
|
1499
|
-
const actualTargets = actual.map(fingerprint).toSorted();
|
|
1500
|
-
return expectedTargets.length === actualTargets.length && expectedTargets.every((target, index) => target === actualTargets[index]);
|
|
1501
|
-
}
|
|
1502
|
-
function formatCodexDiagnosticsUploadResult(sent, failed) {
|
|
1503
|
-
const lines = [];
|
|
1504
|
-
if (sent.length > 0) {
|
|
1505
|
-
lines.push("Codex diagnostics sent to OpenAI servers:");
|
|
1506
|
-
lines.push(...formatCodexDiagnosticsTargetLines(sent));
|
|
1507
|
-
lines.push("Included Codex logs and spawned Codex subthreads when available.");
|
|
1508
|
-
}
|
|
1509
|
-
if (failed.length > 0) {
|
|
1510
|
-
if (lines.length > 0) lines.push("");
|
|
1511
|
-
lines.push("Could not send Codex diagnostics:");
|
|
1512
|
-
lines.push(...failed.map(({ target, error }) => `${formatCodexDiagnosticsTargetLine(target)}: ${formatCodexErrorForDisplay(error)}`));
|
|
1513
|
-
lines.push("Inspect locally:");
|
|
1514
|
-
lines.push(...failed.map(({ target }) => `- ${formatCodexResumeCommandForDisplay(target.threadId)}`));
|
|
1515
|
-
}
|
|
1516
|
-
return lines.join("\n");
|
|
1517
|
-
}
|
|
1518
|
-
function formatCodexDiagnosticsTargetLines(targets) {
|
|
1519
|
-
return targets.flatMap((target, index) => {
|
|
1520
|
-
const lines = formatCodexDiagnosticsTargetBlock(target, index);
|
|
1521
|
-
return index < targets.length - 1 ? [...lines, ""] : lines;
|
|
1522
|
-
});
|
|
1523
|
-
}
|
|
1524
|
-
function formatCodexDiagnosticsTargetBlock(target, index) {
|
|
1525
|
-
const lines = [`Session ${index + 1}`];
|
|
1526
|
-
if (target.channel) lines.push(`Channel: ${formatCodexValueForDisplay(target.channel)}`);
|
|
1527
|
-
if (target.sessionKey) lines.push(`OpenClaw session key: ${formatCodexCopyableValueForDisplay(target.sessionKey)}`);
|
|
1528
|
-
if (target.sessionId) lines.push(`OpenClaw session id: ${formatCodexCopyableValueForDisplay(target.sessionId)}`);
|
|
1529
|
-
lines.push(`Codex thread id: ${formatCodexCopyableValueForDisplay(target.threadId)}`);
|
|
1530
|
-
lines.push(`Inspect locally: ${formatCodexResumeCommandForDisplay(target.threadId)}`);
|
|
1531
|
-
return lines;
|
|
1532
|
-
}
|
|
1533
|
-
function formatCodexDiagnosticsTargetLine(target) {
|
|
1534
|
-
const parts = [];
|
|
1535
|
-
if (target.channel) parts.push(`channel ${formatCodexValueForDisplay(target.channel)}`);
|
|
1536
|
-
const sessionLabel = target.sessionId || target.sessionKey;
|
|
1537
|
-
if (sessionLabel) parts.push(`OpenClaw session ${formatCodexValueForDisplay(sessionLabel)}`);
|
|
1538
|
-
parts.push(`Codex thread ${formatCodexThreadIdForDisplay(target.threadId)}`);
|
|
1539
|
-
return `- ${parts.join(", ")}`;
|
|
1540
|
-
}
|
|
1541
|
-
function normalizeDiagnosticsReason(note) {
|
|
1542
|
-
const normalized = normalizeOptionalString(note);
|
|
1543
|
-
return normalized ? truncateUtf16Safe(normalized, CODEX_DIAGNOSTICS_REASON_MAX_CHARS) : void 0;
|
|
1544
|
-
}
|
|
1545
|
-
function parseDiagnosticsArgs(args) {
|
|
1546
|
-
const [action, token, ...extra] = splitArgs(args);
|
|
1547
|
-
const normalizedAction = action?.toLowerCase();
|
|
1548
|
-
if ((normalizedAction === "confirm" || normalizedAction === "--confirm") && token && extra.length === 0) return {
|
|
1549
|
-
action: "confirm",
|
|
1550
|
-
token
|
|
1551
|
-
};
|
|
1552
|
-
if ((normalizedAction === "cancel" || normalizedAction === "--cancel") && token && extra.length === 0) return {
|
|
1553
|
-
action: "cancel",
|
|
1554
|
-
token
|
|
1555
|
-
};
|
|
1556
|
-
if (normalizedAction === "confirm" || normalizedAction === "--confirm" || normalizedAction === "cancel" || normalizedAction === "--cancel") return { action: "usage" };
|
|
1557
|
-
return {
|
|
1558
|
-
action: "request",
|
|
1559
|
-
note: args
|
|
1560
|
-
};
|
|
1561
|
-
}
|
|
1562
|
-
function formatDiagnosticsUsage(commandPrefix) {
|
|
1563
|
-
return [
|
|
1564
|
-
`Usage: ${commandPrefix} [note]`,
|
|
1565
|
-
`Usage: ${commandPrefix} confirm <token>`,
|
|
1566
|
-
`Usage: ${commandPrefix} cancel <token>`
|
|
1567
|
-
].join("\n");
|
|
1568
|
-
}
|
|
1569
|
-
function createCodexDiagnosticsConfirmation(params) {
|
|
1570
|
-
prunePendingCodexDiagnosticsConfirmations(params.now);
|
|
1571
|
-
if (!pendingCodexDiagnosticsConfirmationTokensByScope.has(params.scopeKey) && pendingCodexDiagnosticsConfirmationTokensByScope.size >= CODEX_DIAGNOSTICS_CONFIRMATION_MAX_SCOPES) {
|
|
1572
|
-
const oldestScopeKey = pendingCodexDiagnosticsConfirmationTokensByScope.keys().next().value;
|
|
1573
|
-
if (typeof oldestScopeKey === "string") deletePendingCodexDiagnosticsConfirmationScope(oldestScopeKey);
|
|
1574
|
-
}
|
|
1575
|
-
const scopeTokens = pendingCodexDiagnosticsConfirmationTokensByScope.get(params.scopeKey) ?? [];
|
|
1576
|
-
while (scopeTokens.length >= CODEX_DIAGNOSTICS_CONFIRMATION_MAX_REQUESTS_PER_SCOPE) {
|
|
1577
|
-
const oldestToken = scopeTokens.shift();
|
|
1578
|
-
if (!oldestToken) break;
|
|
1579
|
-
pendingCodexDiagnosticsConfirmations.delete(oldestToken);
|
|
1580
|
-
}
|
|
1581
|
-
const token = crypto.randomBytes(6).toString("hex");
|
|
1582
|
-
scopeTokens.push(token);
|
|
1583
|
-
pendingCodexDiagnosticsConfirmationTokensByScope.set(params.scopeKey, scopeTokens);
|
|
1584
|
-
pendingCodexDiagnosticsConfirmations.set(token, {
|
|
1585
|
-
token,
|
|
1586
|
-
targets: params.targets,
|
|
1587
|
-
note: params.note,
|
|
1588
|
-
senderId: params.senderId,
|
|
1589
|
-
channel: params.channel,
|
|
1590
|
-
accountId: params.accountId,
|
|
1591
|
-
channelId: params.channelId,
|
|
1592
|
-
messageThreadId: params.messageThreadId,
|
|
1593
|
-
threadParentId: params.threadParentId,
|
|
1594
|
-
sessionKey: params.sessionKey,
|
|
1595
|
-
scopeKey: params.scopeKey,
|
|
1596
|
-
...params.privateRouted === void 0 ? {} : { privateRouted: params.privateRouted },
|
|
1597
|
-
createdAt: params.now
|
|
1598
|
-
});
|
|
1599
|
-
return token;
|
|
1600
|
-
}
|
|
1601
|
-
function readCodexDiagnosticsConfirmationScope(ctx) {
|
|
1602
|
-
return {
|
|
1603
|
-
accountId: normalizeCodexDiagnosticsScopeField(ctx.accountId),
|
|
1604
|
-
channelId: normalizeCodexDiagnosticsScopeField(ctx.channelId),
|
|
1605
|
-
messageThreadId: typeof ctx.messageThreadId === "string" || typeof ctx.messageThreadId === "number" ? normalizeCodexDiagnosticsScopeField(String(ctx.messageThreadId)) : void 0,
|
|
1606
|
-
threadParentId: normalizeCodexDiagnosticsScopeField(ctx.threadParentId),
|
|
1607
|
-
sessionKey: normalizeCodexDiagnosticsScopeField(ctx.sessionKey)
|
|
1608
|
-
};
|
|
1609
|
-
}
|
|
1610
|
-
function readCodexDiagnosticsScopeMismatch(pending, ctx) {
|
|
1611
|
-
const current = readCodexDiagnosticsConfirmationScope(ctx);
|
|
1612
|
-
if (pending.accountId !== current.accountId) return {
|
|
1613
|
-
confirmMessage: "This Codex diagnostics confirmation belongs to a different account.",
|
|
1614
|
-
cancelMessage: "This Codex diagnostics confirmation belongs to a different account."
|
|
1615
|
-
};
|
|
1616
|
-
if (pending.privateRouted) return;
|
|
1617
|
-
if (pending.channelId !== current.channelId) return {
|
|
1618
|
-
confirmMessage: "This Codex diagnostics confirmation belongs to a different channel instance.",
|
|
1619
|
-
cancelMessage: "This Codex diagnostics confirmation belongs to a different channel instance."
|
|
1620
|
-
};
|
|
1621
|
-
if (pending.messageThreadId !== current.messageThreadId) return {
|
|
1622
|
-
confirmMessage: "This Codex diagnostics confirmation belongs to a different thread.",
|
|
1623
|
-
cancelMessage: "This Codex diagnostics confirmation belongs to a different thread."
|
|
1340
|
+
}
|
|
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
|
|
1624
1361
|
};
|
|
1625
|
-
if (
|
|
1626
|
-
|
|
1627
|
-
|
|
1362
|
+
if (binding.connectionScope === "supervision") return {
|
|
1363
|
+
...candidate,
|
|
1364
|
+
threadId: binding.threadId,
|
|
1365
|
+
connectionScope: binding.connectionScope,
|
|
1366
|
+
appServerRuntimeFingerprint: binding.appServerRuntimeFingerprint,
|
|
1367
|
+
pendingSupervisionBranch: binding.pendingSupervisionBranch
|
|
1628
1368
|
};
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
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
|
|
1632
1378
|
};
|
|
1633
1379
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
if (!scopeTokens) return;
|
|
1647
|
-
const tokenIndex = scopeTokens.indexOf(token);
|
|
1648
|
-
if (tokenIndex >= 0) scopeTokens.splice(tokenIndex, 1);
|
|
1649
|
-
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
|
+
}));
|
|
1650
1392
|
}
|
|
1651
|
-
function
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
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.` };
|
|
1655
1441
|
}
|
|
1656
|
-
function
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
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.";
|
|
1660
1449
|
}
|
|
1661
|
-
function
|
|
1662
|
-
|
|
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");
|
|
1663
1475
|
}
|
|
1664
|
-
function
|
|
1665
|
-
|
|
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
|
+
}));
|
|
1666
1485
|
}
|
|
1667
|
-
function
|
|
1668
|
-
|
|
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
|
+
}));
|
|
1669
1494
|
}
|
|
1670
|
-
function
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
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
|
+
});
|
|
1674
1557
|
}
|
|
1675
|
-
function
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
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");
|
|
1680
1569
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
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);
|
|
1683
1593
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
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
|
+
});
|
|
1686
1630
|
}
|
|
1687
|
-
function
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
if (remainingMs === 0) lastCodexDiagnosticsUploadByThread.delete(threadId);
|
|
1692
|
-
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";
|
|
1693
1635
|
}
|
|
1694
|
-
function
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
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;
|
|
1701
1650
|
}
|
|
1702
|
-
const scopeCooldownMs = readCodexDiagnosticsScopeCooldownMs(options.cooldownScope ?? readCodexDiagnosticsCooldownScope(ctx), now);
|
|
1703
|
-
if (scopeCooldownMs > 0) return `Codex diagnostics were already sent for this account or channel recently. Try again in ${Math.ceil(scopeCooldownMs / 1e3)}s.`;
|
|
1704
1651
|
}
|
|
1705
|
-
function
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
if (remainingMs === 0) lastCodexDiagnosticsUploadByScope.delete(scope);
|
|
1710
|
-
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);
|
|
1711
1656
|
}
|
|
1712
|
-
function
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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;
|
|
1716
1663
|
}
|
|
1717
|
-
function
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
}
|
|
1723
|
-
|
|
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));
|
|
1724
1679
|
}
|
|
1725
|
-
function
|
|
1726
|
-
const
|
|
1727
|
-
const
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
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
|
|
1735
1691
|
});
|
|
1736
|
-
|
|
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));
|
|
1737
1716
|
}
|
|
1738
|
-
function
|
|
1739
|
-
|
|
1740
|
-
|
|
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");
|
|
1741
1729
|
}
|
|
1742
|
-
function
|
|
1743
|
-
|
|
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;
|
|
1744
1740
|
}
|
|
1745
|
-
function
|
|
1746
|
-
|
|
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;
|
|
1747
1752
|
}
|
|
1748
|
-
function
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
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
|
+
});
|
|
1752
1772
|
}
|
|
1753
|
-
function
|
|
1754
|
-
|
|
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
|
+
});
|
|
1755
1785
|
}
|
|
1756
|
-
function
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
if (
|
|
1760
|
-
|
|
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
|
+
});
|
|
1761
1798
|
}
|
|
1762
1799
|
async function startThreadAction(deps, ctx, pluginConfig, kind, args) {
|
|
1763
1800
|
const label = kind === "compact" ? "compaction" : "review";
|
|
@@ -1782,273 +1819,248 @@ async function startThreadAction(deps, ctx, pluginConfig, kind, args) {
|
|
|
1782
1819
|
});
|
|
1783
1820
|
return `Started Codex ${label} for thread ${formatCodexDisplayText(binding.threadId)}.`;
|
|
1784
1821
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
current += char;
|
|
1795
|
-
escaping = false;
|
|
1796
|
-
tokenStarted = true;
|
|
1797
|
-
continue;
|
|
1798
|
-
}
|
|
1799
|
-
if (char === "\\" && quote !== "'") {
|
|
1800
|
-
escaping = true;
|
|
1801
|
-
tokenStarted = true;
|
|
1802
|
-
continue;
|
|
1803
|
-
}
|
|
1804
|
-
if (quote) {
|
|
1805
|
-
if (char === quote) quote = void 0;
|
|
1806
|
-
else current += char;
|
|
1807
|
-
tokenStarted = true;
|
|
1808
|
-
continue;
|
|
1809
|
-
}
|
|
1810
|
-
if (char === "\"" || char === "'") {
|
|
1811
|
-
quote = char;
|
|
1812
|
-
tokenStarted = true;
|
|
1813
|
-
continue;
|
|
1814
|
-
}
|
|
1815
|
-
if (/\s/.test(char)) {
|
|
1816
|
-
if (tokenStarted) {
|
|
1817
|
-
args.push(current);
|
|
1818
|
-
current = "";
|
|
1819
|
-
tokenStarted = false;
|
|
1820
|
-
}
|
|
1821
|
-
continue;
|
|
1822
|
-
}
|
|
1823
|
-
current += char;
|
|
1824
|
-
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();
|
|
1825
1831
|
}
|
|
1826
|
-
if (
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
}
|
|
1830
|
-
function parseBindArgs(args) {
|
|
1831
|
-
const parsed = {};
|
|
1832
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1833
|
-
const arg = expectDefined(args[index], "current Codex bind argument");
|
|
1834
|
-
if (arg === "--help" || arg === "-h") {
|
|
1835
|
-
parsed.help = true;
|
|
1836
|
-
continue;
|
|
1837
|
-
}
|
|
1838
|
-
if (arg === "--cwd") {
|
|
1839
|
-
const value = readRequiredOptionValue(args, index);
|
|
1840
|
-
if (!value || parsed.cwd !== void 0) {
|
|
1841
|
-
parsed.help = true;
|
|
1842
|
-
continue;
|
|
1843
|
-
}
|
|
1844
|
-
parsed.cwd = value;
|
|
1845
|
-
index += 1;
|
|
1846
|
-
continue;
|
|
1847
|
-
}
|
|
1848
|
-
if (arg === "--model") {
|
|
1849
|
-
const value = readRequiredOptionValue(args, index);
|
|
1850
|
-
if (!value || parsed.model !== void 0) {
|
|
1851
|
-
parsed.help = true;
|
|
1852
|
-
continue;
|
|
1853
|
-
}
|
|
1854
|
-
parsed.model = value;
|
|
1855
|
-
index += 1;
|
|
1856
|
-
continue;
|
|
1857
|
-
}
|
|
1858
|
-
if (arg === "--provider" || arg === "--model-provider") {
|
|
1859
|
-
const value = readRequiredOptionValue(args, index);
|
|
1860
|
-
if (!value || parsed.provider !== void 0) {
|
|
1861
|
-
parsed.help = true;
|
|
1862
|
-
continue;
|
|
1863
|
-
}
|
|
1864
|
-
parsed.provider = value;
|
|
1865
|
-
index += 1;
|
|
1866
|
-
continue;
|
|
1867
|
-
}
|
|
1868
|
-
if (!arg.startsWith("-") && !parsed.threadId) {
|
|
1869
|
-
parsed.threadId = arg;
|
|
1870
|
-
continue;
|
|
1871
|
-
}
|
|
1872
|
-
parsed.help = true;
|
|
1832
|
+
if (normalized === "help") {
|
|
1833
|
+
if (args.length > 0) return { text: "Usage: /codex plugins help" };
|
|
1834
|
+
return { text: buildPluginsHelp() };
|
|
1873
1835
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
return parsed;
|
|
1879
|
-
}
|
|
1880
|
-
function parseCodexCliSessionsArgs(args) {
|
|
1881
|
-
const parsed = { filter: "" };
|
|
1882
|
-
const filter = [];
|
|
1883
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1884
|
-
const arg = expectDefined(args[index], "current Codex sessions argument");
|
|
1885
|
-
if (arg === "--help" || arg === "-h") {
|
|
1886
|
-
parsed.help = true;
|
|
1887
|
-
continue;
|
|
1888
|
-
}
|
|
1889
|
-
if (arg === "--host" || arg === "--node") {
|
|
1890
|
-
const value = readRequiredOptionValue(args, index);
|
|
1891
|
-
if (!value || parsed.host !== void 0) {
|
|
1892
|
-
parsed.help = true;
|
|
1893
|
-
continue;
|
|
1894
|
-
}
|
|
1895
|
-
parsed.host = value;
|
|
1896
|
-
index += 1;
|
|
1897
|
-
continue;
|
|
1898
|
-
}
|
|
1899
|
-
if (arg === "--limit") {
|
|
1900
|
-
const parsedLimit = parseStrictPositiveInteger(readRequiredOptionValue(args, index));
|
|
1901
|
-
if (parsedLimit === void 0) {
|
|
1902
|
-
parsed.help = true;
|
|
1903
|
-
continue;
|
|
1904
|
-
}
|
|
1905
|
-
parsed.limit = parsedLimit;
|
|
1906
|
-
index += 1;
|
|
1907
|
-
continue;
|
|
1908
|
-
}
|
|
1909
|
-
if (arg.startsWith("-")) {
|
|
1910
|
-
parsed.help = true;
|
|
1911
|
-
continue;
|
|
1912
|
-
}
|
|
1913
|
-
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 }) };
|
|
1914
1840
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
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()}` };
|
|
1918
1859
|
}
|
|
1919
|
-
function
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
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"
|
|
1941
1892
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
continue;
|
|
1945
|
-
}
|
|
1946
|
-
if (!arg.startsWith("-") && !parsed.threadId) {
|
|
1947
|
-
parsed.threadId = arg;
|
|
1948
|
-
continue;
|
|
1949
|
-
}
|
|
1950
|
-
parsed.help = true;
|
|
1951
|
-
}
|
|
1952
|
-
parsed.threadId = normalizeOptionalString(parsed.threadId);
|
|
1953
|
-
parsed.host = normalizeOptionalString(parsed.host);
|
|
1954
|
-
return parsed;
|
|
1893
|
+
])
|
|
1894
|
+
};
|
|
1955
1895
|
}
|
|
1956
|
-
function
|
|
1957
|
-
const
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
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)
|
|
1962
1934
|
};
|
|
1963
|
-
let sawAction = false;
|
|
1964
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1965
|
-
const arg = args[index];
|
|
1966
|
-
if (arg === "--help" || arg === "-h") {
|
|
1967
|
-
parsed.help = true;
|
|
1968
|
-
continue;
|
|
1969
|
-
}
|
|
1970
|
-
if (arg === "status" || arg === "install") {
|
|
1971
|
-
if (sawAction) {
|
|
1972
|
-
parsed.help = true;
|
|
1973
|
-
continue;
|
|
1974
|
-
}
|
|
1975
|
-
sawAction = true;
|
|
1976
|
-
parsed.action = arg;
|
|
1977
|
-
continue;
|
|
1978
|
-
}
|
|
1979
|
-
if (arg === "--source" || arg === "--marketplace-source") {
|
|
1980
|
-
const value = readRequiredOptionValue(args, index);
|
|
1981
|
-
if (!value || parsed.overrides.marketplaceSource !== void 0) {
|
|
1982
|
-
parsed.help = true;
|
|
1983
|
-
continue;
|
|
1984
|
-
}
|
|
1985
|
-
parsed.overrides.marketplaceSource = value;
|
|
1986
|
-
index += 1;
|
|
1987
|
-
continue;
|
|
1988
|
-
}
|
|
1989
|
-
if (arg === "--marketplace-path" || arg === "--path") {
|
|
1990
|
-
const value = readRequiredOptionValue(args, index);
|
|
1991
|
-
if (!value || parsed.overrides.marketplacePath !== void 0) {
|
|
1992
|
-
parsed.help = true;
|
|
1993
|
-
continue;
|
|
1994
|
-
}
|
|
1995
|
-
parsed.overrides.marketplacePath = value;
|
|
1996
|
-
index += 1;
|
|
1997
|
-
continue;
|
|
1998
|
-
}
|
|
1999
|
-
if (arg === "--marketplace") {
|
|
2000
|
-
const value = readRequiredOptionValue(args, index);
|
|
2001
|
-
if (!value || parsed.overrides.marketplaceName !== void 0) {
|
|
2002
|
-
parsed.help = true;
|
|
2003
|
-
continue;
|
|
2004
|
-
}
|
|
2005
|
-
parsed.overrides.marketplaceName = value;
|
|
2006
|
-
index += 1;
|
|
2007
|
-
continue;
|
|
2008
|
-
}
|
|
2009
|
-
if (arg === "--plugin" || arg === "--server" || arg === "--mcp-server") {
|
|
2010
|
-
const value = readRequiredOptionValue(args, index);
|
|
2011
|
-
const configKey = arg === "--plugin" ? "pluginName" : "mcpServerName";
|
|
2012
|
-
if (!value || parsed.persistentIdentity[configKey] !== void 0) {
|
|
2013
|
-
parsed.help = true;
|
|
2014
|
-
continue;
|
|
2015
|
-
}
|
|
2016
|
-
parsed.persistentIdentity[configKey] = value.trim();
|
|
2017
|
-
index += 1;
|
|
2018
|
-
continue;
|
|
2019
|
-
}
|
|
2020
|
-
parsed.help = true;
|
|
2021
|
-
}
|
|
2022
|
-
parsed.overrides = normalizeComputerUseStringOverrides(parsed.overrides);
|
|
2023
|
-
parsed.hasOverrides = Object.values(parsed.overrides).some(Boolean);
|
|
2024
|
-
return parsed;
|
|
2025
1935
|
}
|
|
2026
|
-
function
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
].filter((arg) => Boolean(arg));
|
|
2035
|
-
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");
|
|
2036
1944
|
}
|
|
2037
|
-
function
|
|
2038
|
-
const
|
|
2039
|
-
const
|
|
2040
|
-
if (
|
|
2041
|
-
|
|
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");
|
|
2042
1969
|
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
const
|
|
2048
|
-
if (
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2051
|
-
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()}` };
|
|
2052
2064
|
}
|
|
2053
2065
|
//#endregion
|
|
2054
2066
|
export { handleCodexSubcommand };
|