@chrysb/alphaclaw 0.9.28 → 0.9.30

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.
Files changed (33) hide show
  1. package/README.md +1 -1
  2. package/bin/alphaclaw.js +34 -0
  3. package/lib/node-runtime.js +38 -0
  4. package/lib/public/dist/app.bundle.js +1027 -1020
  5. package/lib/public/js/components/gateway.js +7 -2
  6. package/lib/public/js/components/models-tab/model-picker.js +5 -2
  7. package/lib/public/js/components/models-tab/use-models.js +4 -1
  8. package/lib/public/js/components/nodes-tab/setup-wizard/index.js +3 -1
  9. package/lib/public/js/components/onboarding/welcome-config.js +6 -2
  10. package/lib/public/js/components/onboarding/welcome-setup-step.js +58 -4
  11. package/lib/public/js/components/welcome/use-welcome.js +18 -8
  12. package/lib/public/js/lib/api.js +5 -0
  13. package/lib/public/js/lib/model-config.js +77 -8
  14. package/lib/public/js/lib/thinking-levels.js +1 -0
  15. package/lib/server/agents/shared.js +2 -0
  16. package/lib/server/auth-profiles.js +8 -1
  17. package/lib/server/codex-runtime-config.js +51 -0
  18. package/lib/server/constants.js +18 -0
  19. package/lib/server/cost-utils.js +18 -0
  20. package/lib/server/helpers.js +18 -2
  21. package/lib/server/model-catalog-cache.js +2 -2
  22. package/lib/server/onboarding/index.js +11 -0
  23. package/lib/server/onboarding/openclaw.js +4 -0
  24. package/lib/server/onboarding/validation.js +17 -3
  25. package/lib/server/openclaw-codex-migration.js +61 -17
  26. package/lib/server/openclaw-thinking.js +33 -27
  27. package/lib/server/openclaw-version.js +7 -0
  28. package/lib/server/plugin-config.js +22 -0
  29. package/lib/server/routes/models.js +4 -0
  30. package/lib/server/routes/onboarding.js +33 -0
  31. package/lib/server/usage-tracker-config.js +3 -21
  32. package/lib/server/watchdog.js +43 -0
  33. package/package.json +3 -3
@@ -36,6 +36,8 @@ export const Gateway = ({
36
36
  watchdogStatus?.lifecycle === "crash_loop"
37
37
  ? "crash_loop"
38
38
  : watchdogStatus?.health;
39
+ const hasConfigurationError =
40
+ watchdogStatus?.lifecycle === "configuration_error";
39
41
  const watchdogDotClass =
40
42
  watchdogHealth === "healthy"
41
43
  ? "ac-status-dot ac-status-dot--healthy ac-status-dot--healthy-offset"
@@ -44,8 +46,11 @@ export const Gateway = ({
44
46
  : watchdogHealth === "unhealthy" || watchdogHealth === "crash_loop"
45
47
  ? "bg-red-500"
46
48
  : "bg-gray-500";
47
- const watchdogLabel =
48
- watchdogHealth === "unknown" ? "initializing" : watchdogHealth || "unknown";
49
+ const watchdogLabel = hasConfigurationError
50
+ ? "configuration error"
51
+ : watchdogHealth === "unknown"
52
+ ? "initializing"
53
+ : watchdogHealth || "unknown";
49
54
  const isRepairInProgress = repairing || !!watchdogStatus?.operationInProgress;
50
55
  const showInspectButton = watchdogHealth === "degraded" && !!onOpenWatchdog;
51
56
  const showRepairButton =
@@ -4,6 +4,7 @@ import htm from "htm";
4
4
  import {
5
5
  getModelProvider,
6
6
  getAuthProviderFromModelProvider,
7
+ getFriendlyModelLabel,
7
8
  kProviderLabels,
8
9
  kProviderOrder,
9
10
  } from "../../lib/model-config.js";
@@ -79,11 +80,13 @@ export const buildSyntheticModelEntry = (modelKey) => {
79
80
  return {
80
81
  key,
81
82
  provider,
82
- label: anthropicLabel || key,
83
+ label: anthropicLabel || getFriendlyModelLabel(key),
83
84
  };
84
85
  };
85
86
 
86
- export const getModelDisplayLabel = (model) => model?.featuredLabel || model?.label || model?.key;
87
+ export const getModelDisplayLabel = (model) =>
88
+ model?.featuredLabel ||
89
+ getFriendlyModelLabel(model?.key, model?.label || model?.key);
87
90
 
88
91
  const buildModelSearchText = (model) =>
89
92
  [
@@ -16,6 +16,7 @@ import {
16
16
  kModelCatalogCacheKey,
17
17
  kModelCatalogPollIntervalMs,
18
18
  } from "../../lib/model-catalog.js";
19
+ import { withAlwaysAvailableModels } from "../../lib/model-config.js";
19
20
 
20
21
  let kModelsTabCache = null;
21
22
  const getCredentialValue = (value) =>
@@ -199,7 +200,9 @@ export const useModels = (agentId) => {
199
200
  const addModel = useCallback(
200
201
  (modelKey) => {
201
202
  if (!modelKey) return;
202
- const catalogEntry = catalog.find((model) => model?.key === modelKey);
203
+ const catalogEntry = withAlwaysAvailableModels(catalog).find(
204
+ (model) => model?.key === modelKey,
205
+ );
203
206
  const modelConfig = catalogEntry?.agentRuntime
204
207
  ? { agentRuntime: catalogEntry.agentRuntime }
205
208
  : {};
@@ -100,7 +100,9 @@ export const NodesSetupWizard = ({
100
100
  onCopy: () =>
101
101
  copyAndToast("npm install -g openclaw", "command"),
102
102
  })}
103
- <div class="text-xs text-fg-muted">Requires Node.js 22+.</div>
103
+ <div class="text-xs text-fg-muted">
104
+ Requires Node.js ${">=22.22.3 <23, >=24.15.0 <25, or >=25.9.0."}
105
+ </div>
104
106
  `
105
107
  : null
106
108
  }
@@ -53,12 +53,16 @@ const getAiGroupError = (vals, ctx = {}) => {
53
53
  if (!hasValue(vals.MODEL_KEY) || !String(vals.MODEL_KEY).includes("/")) {
54
54
  return "Choose a model to continue.";
55
55
  }
56
- if (ctx.selectedProvider === "openai-codex" && ctx.codexLoading) {
56
+ if (
57
+ ctx.selectedProvider === "openai-codex" &&
58
+ ctx.codexLoading &&
59
+ !ctx.hasAi
60
+ ) {
57
61
  return "Checking Codex OAuth status. Try Next again in a moment.";
58
62
  }
59
63
  if (!ctx.hasAi) {
60
64
  return ctx.selectedProvider === "openai-codex"
61
- ? "Connect Codex OAuth to continue."
65
+ ? "Connect Codex OAuth or enter an OpenAI API key to continue."
62
66
  : "Add credentials for the selected model provider to continue.";
63
67
  }
64
68
  return "";
@@ -2,6 +2,7 @@ import { h } from "preact";
2
2
  import { useEffect, useState } from "preact/hooks";
3
3
  import htm from "htm";
4
4
  import { LoadingSpinner } from "../loading-spinner.js";
5
+ import { fetchOnboardProgress } from "../../lib/api.js";
5
6
 
6
7
  const html = htm.bind(h);
7
8
  const kSetupTips = [
@@ -30,9 +31,24 @@ const kSetupTips = [
30
31
  text: "Be incredibly careful installing skills from the internet - they may contain malicious code.",
31
32
  },
32
33
  ];
34
+ const kDefaultProgress = {
35
+ stage: "creating_repo",
36
+ message: "Creating repo...",
37
+ };
38
+ const kProgressStepNumbers = {
39
+ creating_repo: 1,
40
+ running_openclaw_onboard: 2,
41
+ initial_git_push: 3,
42
+ starting_gateway: 4,
43
+ };
44
+ const kProgressPollIntervalMs = 500;
45
+ const kProgressDotsIntervalMs = 450;
46
+ const kProgressDotSlots = [1, 2, 3];
33
47
 
34
48
  export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
35
49
  const [tipIndex, setTipIndex] = useState(0);
50
+ const [progress, setProgress] = useState(kDefaultProgress);
51
+ const [progressDotCount, setProgressDotCount] = useState(1);
36
52
 
37
53
  useEffect(() => {
38
54
  if (error || !loading) return;
@@ -42,6 +58,35 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
42
58
  return () => clearInterval(timer);
43
59
  }, [error, loading]);
44
60
 
61
+ useEffect(() => {
62
+ if (error || !loading) return;
63
+ let cancelled = false;
64
+ const refreshProgress = async () => {
65
+ try {
66
+ const progress = await fetchOnboardProgress();
67
+ if (!cancelled && progress?.message) {
68
+ setProgress(progress);
69
+ }
70
+ } catch {}
71
+ };
72
+ setProgress(kDefaultProgress);
73
+ refreshProgress();
74
+ const timer = setInterval(refreshProgress, kProgressPollIntervalMs);
75
+ return () => {
76
+ cancelled = true;
77
+ clearInterval(timer);
78
+ };
79
+ }, [error, loading]);
80
+
81
+ useEffect(() => {
82
+ if (error || !loading) return;
83
+ setProgressDotCount(1);
84
+ const timer = setInterval(() => {
85
+ setProgressDotCount((count) => (count % 3) + 1);
86
+ }, kProgressDotsIntervalMs);
87
+ return () => clearInterval(timer);
88
+ }, [error, loading]);
89
+
45
90
  if (error) {
46
91
  return html`
47
92
  <div class="py-4 flex flex-col items-center text-center gap-3">
@@ -77,6 +122,8 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
77
122
  }
78
123
 
79
124
  const currentTip = kSetupTips[tipIndex];
125
+ const progressStep = kProgressStepNumbers[progress.stage] || 1;
126
+ const progressLabel = (progress.message || kDefaultProgress.message).replace(/\.{1,3}$/, "");
80
127
 
81
128
  return html`
82
129
  <div class="relative min-h-[320px] pt-4 pb-20 flex">
@@ -84,10 +131,17 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
84
131
  class="flex-1 flex flex-col items-center justify-center text-center gap-4"
85
132
  >
86
133
  <${LoadingSpinner} className="h-8 w-8 text-body" />
87
- <h3 class="text-lg font-semibold text-body">
88
- Initializing OpenClaw...
89
- </h3>
90
- <p class="text-sm text-fg-muted">This could take 10-15 seconds</p>
134
+ <h3 class="text-lg font-semibold text-body">Initializing AlphaClaw...</h3>
135
+ <p class="text-sm text-fg-muted">
136
+ ${progressStep} / 4: ${progressLabel}<span class="inline-flex" aria-hidden="true"
137
+ >${kProgressDotSlots.map(
138
+ (slot) => html`<span style=${{ visibility: slot <= progressDotCount ? "visible" : "hidden" }}
139
+ >.</span
140
+ >`,
141
+ )}</span
142
+ >
143
+ </p>
144
+ <p class="text-xs text-fg-muted">This could take up to 30 seconds</p>
91
145
  </div>
92
146
  <div
93
147
  class="absolute bottom-3 left-3 right-3 bg-field border border-border rounded-lg px-3 py-2 text-xs text-fg-muted"
@@ -9,11 +9,14 @@ import {
9
9
  import { useCachedFetch } from "../../hooks/use-cached-fetch.js";
10
10
  import { usePolling } from "../../hooks/usePolling.js";
11
11
  import {
12
- getModelProvider,
13
12
  getAuthProviderFromModelProvider,
14
13
  getFeaturedModels,
14
+ getOnboardingModelProvider,
15
+ getOnboardingModels,
16
+ isDeprecatedOnboardingModelKey,
15
17
  getVisibleAiFieldKeys,
16
18
  kProviderAuthFields,
19
+ withAlwaysAvailableModels,
17
20
  } from "../../lib/model-config.js";
18
21
  import {
19
22
  getInitialOnboardingModelKey,
@@ -167,7 +170,9 @@ export const useWelcome = ({ onComplete }) => {
167
170
  };
168
171
 
169
172
  const applyModelCatalog = useCallback((payload) => {
170
- const list = getModelCatalogModels(payload);
173
+ const list = getOnboardingModels(
174
+ withAlwaysAvailableModels(getModelCatalogModels(payload)),
175
+ );
171
176
  if (!payload) return;
172
177
  const isRefreshing = isModelCatalogRefreshing(payload);
173
178
  const isFallbackRefresh =
@@ -181,11 +186,14 @@ export const useWelcome = ({ onComplete }) => {
181
186
  : null
182
187
  : "No models found",
183
188
  );
189
+ const currentModelIsDeprecated = isDeprecatedOnboardingModelKey(
190
+ vals.MODEL_KEY,
191
+ );
184
192
  const defaultModelKey = getInitialOnboardingModelKey({
185
193
  catalog: list,
186
- currentModelKey: vals.MODEL_KEY,
194
+ currentModelKey: currentModelIsDeprecated ? "" : vals.MODEL_KEY,
187
195
  });
188
- if (!vals.MODEL_KEY && defaultModelKey) {
196
+ if ((!vals.MODEL_KEY || currentModelIsDeprecated) && defaultModelKey) {
189
197
  setVals((prev) => ({ ...prev, MODEL_KEY: defaultModelKey }));
190
198
  }
191
199
  }, [setVals, vals.MODEL_KEY]);
@@ -212,16 +220,18 @@ export const useWelcome = ({ onComplete }) => {
212
220
  }, [modelsFetchState.error]);
213
221
 
214
222
  const getValidationContext = (currentVals = {}) => {
215
- const currentSelectedProvider = getModelProvider(
216
- String(currentVals.MODEL_KEY || "").trim(),
217
- );
223
+ const currentSelectedProvider = getOnboardingModelProvider({
224
+ modelKey: currentVals.MODEL_KEY,
225
+ models,
226
+ });
218
227
  const currentSelectedAuthProvider =
219
228
  getAuthProviderFromModelProvider(currentSelectedProvider);
220
229
  const currentProviderAuthFields =
221
230
  kProviderAuthFields[currentSelectedAuthProvider] || [];
222
231
  const currentHasAi =
223
232
  currentSelectedProvider === "openai-codex"
224
- ? !!codexStatus.connected
233
+ ? !!codexStatus.connected ||
234
+ !!String(currentVals.OPENAI_API_KEY || "").trim()
225
235
  : currentProviderAuthFields.some((field) =>
226
236
  !!String(currentVals[field.key] || "").trim(),
227
237
  );
@@ -818,6 +818,11 @@ export async function fetchOnboardStatus() {
818
818
  return res.json();
819
819
  }
820
820
 
821
+ export async function fetchOnboardProgress() {
822
+ const res = await authFetch("/api/onboard/progress");
823
+ return res.json();
824
+ }
825
+
821
826
  export async function runOnboard(vars, modelKey, { importMode = false } = {}) {
822
827
  const res = await authFetch("/api/onboard", {
823
828
  method: "POST",
@@ -26,11 +26,8 @@ export const kFeaturedModelDefs = [
26
26
  preferredKeys: ["anthropic/claude-sonnet-4-6"],
27
27
  },
28
28
  {
29
- label: "Codex 5.3",
30
- preferredKeys: [
31
- "openai/gpt-5.3-codex",
32
- "openai-codex/gpt-5.3-codex",
33
- ],
29
+ label: "GPT-5.6 Sol",
30
+ preferredKeys: ["openai/gpt-5.6-sol", "openai-codex/gpt-5.6-sol"],
34
31
  },
35
32
  {
36
33
  label: "GPT-5.5",
@@ -42,7 +39,70 @@ export const kFeaturedModelDefs = [
42
39
  },
43
40
  ];
44
41
 
42
+ const kDeprecatedOnboardingModelKeys = new Set([
43
+ "openai/gpt-5.3-codex",
44
+ "openai-codex/gpt-5.3-codex",
45
+ ]);
46
+ const kCanonicalCodexOauthModelKeys = new Set([
47
+ "openai/gpt-5.4-mini",
48
+ "openai/gpt-5.5",
49
+ "openai/gpt-5.6-sol",
50
+ "openai/gpt-5.6-terra",
51
+ "openai/gpt-5.6-luna",
52
+ ]);
53
+ const kFriendlyModelLabels = {
54
+ "openai/gpt-5.4-mini": "GPT-5.4 Mini",
55
+ "openai/gpt-5.5": "GPT-5.5",
56
+ "openai/gpt-5.6-sol": "GPT-5.6 Sol",
57
+ "openai/gpt-5.6-terra": "GPT-5.6 Terra",
58
+ "openai/gpt-5.6-luna": "GPT-5.6 Luna",
59
+ };
60
+
61
+ export const getFriendlyModelLabel = (modelKey, fallback = "") => {
62
+ const normalizedKey = String(modelKey || "").trim();
63
+ return kFriendlyModelLabels[normalizedKey] || fallback || normalizedKey;
64
+ };
65
+
66
+ export const isDeprecatedOnboardingModelKey = (modelKey) =>
67
+ kDeprecatedOnboardingModelKeys.has(String(modelKey || "").trim());
68
+
69
+ export const getOnboardingModels = (models = []) =>
70
+ models.filter(
71
+ (model) => !isDeprecatedOnboardingModelKey(model?.key),
72
+ );
73
+
74
+ export const getOnboardingModelProvider = ({ modelKey, models = [] } = {}) => {
75
+ const normalizedKey = String(modelKey || "").trim();
76
+ const model = models.find((candidate) => candidate?.key === normalizedKey);
77
+ if (
78
+ getModelProvider(normalizedKey) === "openai-codex" ||
79
+ kCanonicalCodexOauthModelKeys.has(normalizedKey) ||
80
+ model?.agentRuntime?.id === "codex"
81
+ ) {
82
+ return "openai-codex";
83
+ }
84
+ return getModelProvider(normalizedKey);
85
+ };
86
+
45
87
  export const kAlwaysAvailableModelDefs = [
88
+ {
89
+ key: "openai/gpt-5.6-sol",
90
+ provider: "openai",
91
+ label: "GPT-5.6 Sol",
92
+ agentRuntime: { id: "codex" },
93
+ },
94
+ {
95
+ key: "openai/gpt-5.6-terra",
96
+ provider: "openai",
97
+ label: "GPT-5.6 Terra",
98
+ agentRuntime: { id: "codex" },
99
+ },
100
+ {
101
+ key: "openai/gpt-5.6-luna",
102
+ provider: "openai",
103
+ label: "GPT-5.6 Luna",
104
+ agentRuntime: { id: "codex" },
105
+ },
46
106
  {
47
107
  key: "openai/gpt-5.4-mini",
48
108
  provider: "openai",
@@ -59,9 +119,19 @@ export const kAlwaysAvailableModelDefs = [
59
119
 
60
120
  export const withAlwaysAvailableModels = (models = []) => {
61
121
  const merged = [...models];
62
- const keys = new Set(merged.map((model) => model?.key));
63
122
  for (const model of kAlwaysAvailableModelDefs) {
64
- if (!keys.has(model.key)) merged.push(model);
123
+ const existingIndex = merged.findIndex((candidate) => candidate?.key === model.key);
124
+ if (existingIndex < 0) {
125
+ merged.push(model);
126
+ continue;
127
+ }
128
+ const existing = merged[existingIndex];
129
+ merged[existingIndex] = {
130
+ ...model,
131
+ ...existing,
132
+ label: getFriendlyModelLabel(model.key, existing?.label || model.label),
133
+ agentRuntime: existing?.agentRuntime || model.agentRuntime,
134
+ };
65
135
  }
66
136
  return merged;
67
137
  };
@@ -345,7 +415,6 @@ export const kFeatureDefs = [
345
415
  ];
346
416
 
347
417
  export const getVisibleAiFieldKeys = (provider) => {
348
- if (provider === "openai-codex") return new Set();
349
418
  const authProvider = getAuthProviderFromModelProvider(provider);
350
419
  const fields = kProviderAuthFields[authProvider] || [];
351
420
  return new Set(fields.map((field) => field.key));
@@ -8,6 +8,7 @@ const kThinkingLevelLabelOverrides = {
8
8
  adaptive: "Adaptive",
9
9
  xhigh: "Extra high",
10
10
  max: "Maximum",
11
+ ultra: "Ultra",
11
12
  };
12
13
 
13
14
  export const formatThinkingLevelLabel = (levelId = "") => {
@@ -3,6 +3,7 @@ const {
3
3
  readOpenclawConfig,
4
4
  writeOpenclawConfig,
5
5
  } = require("../openclaw-config");
6
+ const { ensureCodexRuntimePlugin } = require("../codex-runtime-config");
6
7
 
7
8
  const kDefaultAgentId = "main";
8
9
  const kAgentIdPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
@@ -105,6 +106,7 @@ const loadConfig = ({ fsImpl, OPENCLAW_DIR }) =>
105
106
  });
106
107
 
107
108
  const saveConfig = ({ fsImpl, OPENCLAW_DIR, config }) => {
109
+ ensureCodexRuntimePlugin(config);
108
110
  writeOpenclawConfig({
109
111
  fsModule: fsImpl,
110
112
  openclawDir: OPENCLAW_DIR,
@@ -2,6 +2,7 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { DatabaseSync } = require("node:sqlite");
4
4
  const { AUTH_PROFILES_PATH, CODEX_PROFILE_ID, OPENCLAW_DIR } = require("./constants");
5
+ const { ensureCodexRuntimePlugin } = require("./codex-runtime-config");
5
6
 
6
7
  const kDefaultAgentId = "main";
7
8
  const kApiKeyEnvVarByProvider = {
@@ -368,8 +369,13 @@ const createAuthProfiles = () => {
368
369
  const cfg = loadOpenclawConfig();
369
370
  const defaults = cfg.agents?.defaults || {};
370
371
  const configuredModels = preserveCodexRuntimeModels(defaults.models || {});
371
- if (JSON.stringify(configuredModels) !== JSON.stringify(defaults.models || {})) {
372
+ const modelsChanged =
373
+ JSON.stringify(configuredModels) !== JSON.stringify(defaults.models || {});
374
+ if (modelsChanged) {
372
375
  cfg.agents.defaults.models = configuredModels;
376
+ }
377
+ const pluginsChanged = ensureCodexRuntimePlugin(cfg);
378
+ if (modelsChanged || pluginsChanged) {
373
379
  saveOpenclawConfig(cfg);
374
380
  }
375
381
  return {
@@ -389,6 +395,7 @@ const createAuthProfiles = () => {
389
395
  if (configuredModels !== undefined) {
390
396
  cfg.agents.defaults.models = preserveCodexRuntimeModels(configuredModels);
391
397
  }
398
+ ensureCodexRuntimePlugin(cfg);
392
399
  saveOpenclawConfig(cfg);
393
400
  };
394
401
 
@@ -0,0 +1,51 @@
1
+ const {
2
+ ensurePluginsShell,
3
+ ensurePluginAllowed,
4
+ } = require("./plugin-config");
5
+
6
+ const modelUsesCodexRuntime = (modelKey, modelConfig) => {
7
+ const runtimeId = String(modelConfig?.agentRuntime?.id || "").trim();
8
+ if (runtimeId === "codex") return true;
9
+ if (runtimeId === "openclaw") return false;
10
+ return String(modelKey || "").startsWith("openai/gpt-");
11
+ };
12
+
13
+ const configUsesCodexRuntime = (cfg = {}) => {
14
+ const scopes = [
15
+ cfg.agents?.defaults || {},
16
+ ...(Array.isArray(cfg.agents?.list) ? cfg.agents.list : []),
17
+ ];
18
+ return scopes.some((scope) => {
19
+ const configuredModels = scope.models || {};
20
+ if (
21
+ Object.entries(configuredModels).some(([modelKey, modelConfig]) =>
22
+ modelUsesCodexRuntime(modelKey, modelConfig),
23
+ )
24
+ ) {
25
+ return true;
26
+ }
27
+ const primary = String(scope.model?.primary || "").trim();
28
+ return (
29
+ !!primary && modelUsesCodexRuntime(primary, configuredModels[primary])
30
+ );
31
+ });
32
+ };
33
+
34
+ const ensureCodexRuntimePlugin = (cfg = {}) => {
35
+ if (!configUsesCodexRuntime(cfg)) return false;
36
+ const before = JSON.stringify(cfg.plugins || {});
37
+ ensurePluginsShell(cfg);
38
+ ensurePluginAllowed({ cfg, pluginKey: "codex" });
39
+ const existingEntry = cfg.plugins.entries.codex;
40
+ cfg.plugins.entries.codex = {
41
+ ...(existingEntry && typeof existingEntry === "object" ? existingEntry : {}),
42
+ enabled: true,
43
+ };
44
+ return JSON.stringify(cfg.plugins) !== before;
45
+ };
46
+
47
+ module.exports = {
48
+ configUsesCodexRuntime,
49
+ ensureCodexRuntimePlugin,
50
+ modelUsesCodexRuntime,
51
+ };
@@ -172,6 +172,24 @@ const kMinimalFallbackOnboardingModels = [
172
172
  provider: "anthropic",
173
173
  label: "Claude Haiku 4.6",
174
174
  },
175
+ {
176
+ key: "openai/gpt-5.6-sol",
177
+ provider: "openai",
178
+ label: "GPT-5.6 Sol",
179
+ agentRuntime: { id: "codex" },
180
+ },
181
+ {
182
+ key: "openai/gpt-5.6-terra",
183
+ provider: "openai",
184
+ label: "GPT-5.6 Terra",
185
+ agentRuntime: { id: "codex" },
186
+ },
187
+ {
188
+ key: "openai/gpt-5.6-luna",
189
+ provider: "openai",
190
+ label: "GPT-5.6 Luna",
191
+ agentRuntime: { id: "codex" },
192
+ },
175
193
  {
176
194
  key: "openai/gpt-5.5",
177
195
  provider: "openai",
@@ -48,6 +48,24 @@ const kGlobalModelPricing = {
48
48
  "claude-haiku-4-6": { input: 0.8, output: 4.0 },
49
49
  "gpt-5": { input: 1.25, output: 10.0 },
50
50
  "gpt-5.4": { input: 2.5, output: 10.0 },
51
+ "gpt-5.6-sol": {
52
+ input: 5.0,
53
+ output: 30.0,
54
+ cacheRead: 0.5,
55
+ cacheWrite: 6.25,
56
+ },
57
+ "gpt-5.6-terra": {
58
+ input: 2.5,
59
+ output: 15.0,
60
+ cacheRead: 0.25,
61
+ cacheWrite: 3.125,
62
+ },
63
+ "gpt-5.6-luna": {
64
+ input: 1.0,
65
+ output: 6.0,
66
+ cacheRead: 0.1,
67
+ cacheWrite: 1.25,
68
+ },
51
69
  "gpt-5.1-codex": { input: 2.5, output: 10.0 },
52
70
  "gpt-5.3-codex": { input: 2.5, output: 10.0 },
53
71
  "gpt-4.1": { input: 2.0, output: 8.0 },
@@ -123,13 +123,29 @@ const normalizeOnboardingModels = (models) => {
123
123
  const provider = resolveModelProvider(key);
124
124
  if (!kOnboardingModelProviders.has(provider)) continue;
125
125
  if (!deduped.has(key)) {
126
+ const agentRuntime = isCodexRuntimeModel
127
+ ? { id: "codex" }
128
+ : model.agentRuntime;
126
129
  deduped.set(key, {
127
130
  key,
128
131
  provider,
129
132
  label: model.name || model.key,
130
- ...(isCodexRuntimeModel
131
- ? { agentRuntime: { id: "codex" } }
133
+ ...(typeof model.available === "boolean"
134
+ ? { available: model.available }
132
135
  : {}),
136
+ ...(typeof model.reasoning === "boolean"
137
+ ? { reasoning: model.reasoning }
138
+ : {}),
139
+ ...(Number.isFinite(model.contextWindow)
140
+ ? { contextWindow: model.contextWindow }
141
+ : {}),
142
+ ...(Number.isFinite(model.maxTokens)
143
+ ? { maxTokens: model.maxTokens }
144
+ : {}),
145
+ ...(model.compat && typeof model.compat === "object"
146
+ ? { compat: model.compat }
147
+ : {}),
148
+ ...(agentRuntime ? { agentRuntime } : {}),
133
149
  });
134
150
  }
135
151
  }
@@ -3,7 +3,7 @@ const path = require("path");
3
3
  const { ALPHACLAW_DIR, kFallbackOnboardingModels } = require("./constants");
4
4
  const { getCommandOutputCandidates } = require("./utils/command-output");
5
5
 
6
- const kModelCatalogCacheVersion = 1;
6
+ const kModelCatalogCacheVersion = 2;
7
7
  const kModelCatalogRefreshBackoffMs = 30 * 1000;
8
8
  const kModelCatalogLoadTimeoutMs = 120 * 1000;
9
9
  const kModelCatalogBootstrapSource = "bootstrap";
@@ -36,9 +36,9 @@ const normalizeCachedModels = ({
36
36
  } = {}) =>
37
37
  normalizeOnboardingModels(
38
38
  (Array.isArray(models) ? models : []).map((model) => ({
39
+ ...model,
39
40
  key: model?.key,
40
41
  name: model?.label || model?.name || model?.key,
41
- ...(model?.agentRuntime ? { agentRuntime: model.agentRuntime } : {}),
42
42
  })),
43
43
  );
44
44
 
@@ -369,7 +369,14 @@ const createOnboardingService = ({
369
369
  vars,
370
370
  modelKey,
371
371
  importMode = false,
372
+ onProgress,
372
373
  }) => {
374
+ const reportProgress = (stage) => {
375
+ if (typeof onProgress !== "function") return;
376
+ try {
377
+ onProgress(stage);
378
+ } catch {}
379
+ };
373
380
  const validation = validateOnboardingInput({
374
381
  vars,
375
382
  modelKey,
@@ -425,6 +432,7 @@ const createOnboardingService = ({
425
432
  syncApiKeyAuthProfilesFromEnvVars(authProfiles, varsToSave);
426
433
 
427
434
  const [, repoName] = repoUrl.split("/");
435
+ reportProgress("creating_repo");
428
436
  const repoCheck = await ensureGithubRepoAccessible({
429
437
  repoUrl,
430
438
  repoName,
@@ -481,6 +489,7 @@ const createOnboardingService = ({
481
489
  );
482
490
  }
483
491
 
492
+ reportProgress("running_openclaw_onboard");
484
493
  if (!existingConfigPresent) {
485
494
  const onboardArgs = buildOnboardArgs({
486
495
  varMap,
@@ -551,6 +560,7 @@ const createOnboardingService = ({
551
560
 
552
561
  ensureGatewayProxyConfig(getBaseUrl(req));
553
562
 
563
+ reportProgress("initial_git_push");
554
564
  try {
555
565
  const commitMsg = importMode
556
566
  ? "imported existing setup via AlphaClaw"
@@ -567,6 +577,7 @@ const createOnboardingService = ({
567
577
  console.error("[onboard] Git push error:", e.message);
568
578
  }
569
579
 
580
+ reportProgress("starting_gateway");
570
581
  runOnboardedBootSequence();
571
582
  return { status: 200, body: { ok: true } };
572
583
  };
@@ -5,6 +5,7 @@ const {
5
5
  ensureUsageTrackerPluginEntry,
6
6
  } = require("../usage-tracker-config");
7
7
  const { isOpenAiCompatApiEnabled } = require("../alphaclaw-config");
8
+ const { ensureCodexRuntimePlugin } = require("../codex-runtime-config");
8
9
 
9
10
  const kDefaultToolsProfile = "full";
10
11
  const kBootstrapExtraFiles = [
@@ -248,6 +249,7 @@ const writeSanitizedOpenclawConfig = ({
248
249
  cfg,
249
250
  varMap,
250
251
  });
252
+ ensureCodexRuntimePlugin(cfg);
251
253
 
252
254
  let content = JSON.stringify(cfg, null, 2);
253
255
  const replacements = buildSecretReplacements(varMap, process.env);
@@ -357,6 +359,8 @@ const writeManagedImportOpenclawConfig = ({
357
359
  ensurePluginAllowed({ cfg, pluginKey: "whatsapp" });
358
360
  }
359
361
 
362
+ ensureCodexRuntimePlugin(cfg);
363
+
360
364
  fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
361
365
  };
362
366