@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.
- package/README.md +1 -1
- package/bin/alphaclaw.js +34 -0
- package/lib/node-runtime.js +38 -0
- package/lib/public/dist/app.bundle.js +1027 -1020
- package/lib/public/js/components/gateway.js +7 -2
- package/lib/public/js/components/models-tab/model-picker.js +5 -2
- package/lib/public/js/components/models-tab/use-models.js +4 -1
- package/lib/public/js/components/nodes-tab/setup-wizard/index.js +3 -1
- package/lib/public/js/components/onboarding/welcome-config.js +6 -2
- package/lib/public/js/components/onboarding/welcome-setup-step.js +58 -4
- package/lib/public/js/components/welcome/use-welcome.js +18 -8
- package/lib/public/js/lib/api.js +5 -0
- package/lib/public/js/lib/model-config.js +77 -8
- package/lib/public/js/lib/thinking-levels.js +1 -0
- package/lib/server/agents/shared.js +2 -0
- package/lib/server/auth-profiles.js +8 -1
- package/lib/server/codex-runtime-config.js +51 -0
- package/lib/server/constants.js +18 -0
- package/lib/server/cost-utils.js +18 -0
- package/lib/server/helpers.js +18 -2
- package/lib/server/model-catalog-cache.js +2 -2
- package/lib/server/onboarding/index.js +11 -0
- package/lib/server/onboarding/openclaw.js +4 -0
- package/lib/server/onboarding/validation.js +17 -3
- package/lib/server/openclaw-codex-migration.js +61 -17
- package/lib/server/openclaw-thinking.js +33 -27
- package/lib/server/openclaw-version.js +7 -0
- package/lib/server/plugin-config.js +22 -0
- package/lib/server/routes/models.js +4 -0
- package/lib/server/routes/onboarding.js +33 -0
- package/lib/server/usage-tracker-config.js +3 -21
- package/lib/server/watchdog.js +43 -0
- package/package.json +3 -3
|
@@ -2,6 +2,17 @@ const { getEnvVarForApiKeyProvider } = require("../auth-profiles");
|
|
|
2
2
|
|
|
3
3
|
const kAnthropicSetupTokenPrefix = "sk-ant-oat01-";
|
|
4
4
|
const kAnthropicApiKeyPrefix = "sk-ant-api";
|
|
5
|
+
const kCanonicalCodexOauthModelKeys = new Set([
|
|
6
|
+
"openai/gpt-5.4-mini",
|
|
7
|
+
"openai/gpt-5.5",
|
|
8
|
+
"openai/gpt-5.6-sol",
|
|
9
|
+
"openai/gpt-5.6-terra",
|
|
10
|
+
"openai/gpt-5.6-luna",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const usesCodexOauth = (modelKey, provider) =>
|
|
14
|
+
provider === "openai-codex" ||
|
|
15
|
+
kCanonicalCodexOauthModelKeys.has(String(modelKey || "").trim());
|
|
5
16
|
|
|
6
17
|
const validateAnthropicCredentialShape = (varMap) => {
|
|
7
18
|
const anthropicToken = String(varMap.ANTHROPIC_TOKEN || "").trim();
|
|
@@ -71,7 +82,10 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
|
|
|
71
82
|
if (!anthropicValidation.ok) return anthropicValidation;
|
|
72
83
|
const githubToken = String(varMap.GITHUB_TOKEN || "");
|
|
73
84
|
const githubRepoInput = String(varMap.GITHUB_WORKSPACE_REPO || "").trim();
|
|
74
|
-
const
|
|
85
|
+
const resolvedProvider = resolveModelProvider(modelKey);
|
|
86
|
+
const selectedProvider = usesCodexOauth(modelKey, resolvedProvider)
|
|
87
|
+
? "openai-codex"
|
|
88
|
+
: resolvedProvider;
|
|
75
89
|
const hasCodexOauth = hasCodexOauthProfile();
|
|
76
90
|
const hasAnyAi = !!(
|
|
77
91
|
varMap.ANTHROPIC_API_KEY ||
|
|
@@ -82,7 +96,7 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
|
|
|
82
96
|
);
|
|
83
97
|
const hasAi = (() => {
|
|
84
98
|
if (selectedProvider === "openai-codex") {
|
|
85
|
-
return hasCodexOauth;
|
|
99
|
+
return hasCodexOauth || !!String(varMap.OPENAI_API_KEY || "").trim();
|
|
86
100
|
}
|
|
87
101
|
if (selectedProvider === "anthropic") {
|
|
88
102
|
return !!(varMap.ANTHROPIC_API_KEY || varMap.ANTHROPIC_TOKEN);
|
|
@@ -101,7 +115,7 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
|
|
|
101
115
|
return {
|
|
102
116
|
ok: false,
|
|
103
117
|
status: 400,
|
|
104
|
-
error: "Connect OpenAI Codex OAuth before continuing",
|
|
118
|
+
error: "Connect OpenAI Codex OAuth or add an OpenAI API key before continuing",
|
|
105
119
|
};
|
|
106
120
|
}
|
|
107
121
|
return {
|
|
@@ -3,14 +3,36 @@ const path = require("path");
|
|
|
3
3
|
const { pathToFileURL } = require("url");
|
|
4
4
|
const { DatabaseSync } = require("node:sqlite");
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const findOpenclawDistModules = (prefix) => {
|
|
7
7
|
const entryPath = require.resolve("openclaw");
|
|
8
8
|
const distDir = path.dirname(entryPath);
|
|
9
|
-
const
|
|
9
|
+
const filenames = fs
|
|
10
10
|
.readdirSync(distDir)
|
|
11
|
-
.
|
|
12
|
-
if (
|
|
13
|
-
|
|
11
|
+
.filter((name) => name.startsWith(`${prefix}-`) && name.endsWith(".js"));
|
|
12
|
+
if (filenames.length === 0) {
|
|
13
|
+
throw new Error(`OpenClaw migration module not found: ${prefix}`);
|
|
14
|
+
}
|
|
15
|
+
return filenames.map((filename) => path.join(distDir, filename));
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const loadOpenclawMigrationApi = async ({ prefix, functionNames }) => {
|
|
19
|
+
const api = {};
|
|
20
|
+
for (const modulePath of findOpenclawDistModules(prefix)) {
|
|
21
|
+
const mod = await import(pathToFileURL(modulePath).href);
|
|
22
|
+
for (const candidate of Object.values(mod)) {
|
|
23
|
+
if (typeof candidate !== "function") continue;
|
|
24
|
+
if (functionNames.includes(candidate.name) && !api[candidate.name]) {
|
|
25
|
+
api[candidate.name] = candidate;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const missing = functionNames.filter((name) => typeof api[name] !== "function");
|
|
30
|
+
if (missing.length > 0) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`OpenClaw migration exports not found for ${prefix}: ${missing.join(", ")}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return api;
|
|
14
36
|
};
|
|
15
37
|
|
|
16
38
|
const writeConfig = (configPath, cfg) => {
|
|
@@ -73,33 +95,55 @@ const migrateLegacyCodexState = async ({
|
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
const routeApi = await loadOpenclawMigrationApi({
|
|
99
|
+
prefix: "codex-route-warnings",
|
|
100
|
+
functionNames: [
|
|
101
|
+
"maybeRepairCodexRoutes",
|
|
102
|
+
"maybeRepairCodexSessionRoutes",
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
const authApi = await loadOpenclawMigrationApi({
|
|
106
|
+
prefix: "doctor-auth-flat-profiles",
|
|
107
|
+
functionNames: [
|
|
108
|
+
"collectOpenAICodexAuthProfileStoreIdMap",
|
|
109
|
+
"maybeRepairOpenAICodexAuthConfig",
|
|
110
|
+
"maybeRepairOpenAICodexAuthProfileStores",
|
|
111
|
+
"maybeMigrateAuthProfileJsonStoresToSqlite",
|
|
112
|
+
],
|
|
113
|
+
});
|
|
82
114
|
|
|
83
115
|
const changes = [];
|
|
84
116
|
const warnings = [];
|
|
85
|
-
const routeRepair =
|
|
117
|
+
const routeRepair = routeApi.maybeRepairCodexRoutes({
|
|
118
|
+
cfg,
|
|
119
|
+
env,
|
|
120
|
+
shouldRepair: true,
|
|
121
|
+
});
|
|
86
122
|
let nextCfg = routeRepair.cfg;
|
|
87
123
|
changes.push(...routeRepair.changes);
|
|
88
124
|
warnings.push(...routeRepair.warnings);
|
|
89
125
|
|
|
90
|
-
const profileIdMap =
|
|
91
|
-
|
|
126
|
+
const profileIdMap = authApi.collectOpenAICodexAuthProfileStoreIdMap({
|
|
127
|
+
cfg: nextCfg,
|
|
128
|
+
env,
|
|
129
|
+
});
|
|
130
|
+
const configAuthRepair = authApi.maybeRepairOpenAICodexAuthConfig(nextCfg, {
|
|
131
|
+
profileIdMap,
|
|
132
|
+
});
|
|
92
133
|
nextCfg = configAuthRepair.config;
|
|
93
134
|
changes.push(...configAuthRepair.changes);
|
|
94
135
|
warnings.push(...configAuthRepair.warnings);
|
|
95
136
|
|
|
96
137
|
if (changes.length > 0) writeConfig(configPath, nextCfg);
|
|
97
138
|
|
|
98
|
-
const storeRepair = await
|
|
139
|
+
const storeRepair = await authApi.maybeRepairOpenAICodexAuthProfileStores({
|
|
140
|
+
cfg: nextCfg,
|
|
141
|
+
env,
|
|
142
|
+
});
|
|
99
143
|
changes.push(...storeRepair.changes);
|
|
100
144
|
warnings.push(...storeRepair.warnings);
|
|
101
145
|
|
|
102
|
-
const sqliteMigration = await
|
|
146
|
+
const sqliteMigration = await authApi.maybeMigrateAuthProfileJsonStoresToSqlite({
|
|
103
147
|
cfg: nextCfg,
|
|
104
148
|
env,
|
|
105
149
|
prompter: { confirmAutoFix: async () => true },
|
|
@@ -113,7 +157,7 @@ const migrateLegacyCodexState = async ({
|
|
|
113
157
|
writeConfig(configPath, nextCfg);
|
|
114
158
|
}
|
|
115
159
|
|
|
116
|
-
const sessionRepair = await
|
|
160
|
+
const sessionRepair = await routeApi.maybeRepairCodexSessionRoutes({
|
|
117
161
|
cfg: nextCfg,
|
|
118
162
|
env,
|
|
119
163
|
shouldRepair: true,
|
|
@@ -27,29 +27,28 @@ const loadThinkingModule = async () => {
|
|
|
27
27
|
return thinkingModulePromise;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
const normalizeThinkingLevel = (raw) => {
|
|
31
|
+
const key = String(raw || "").trim().toLowerCase();
|
|
32
|
+
if (!key) return null;
|
|
33
|
+
const collapsed = key.replace(/[\s_-]+/g, "");
|
|
34
|
+
if (collapsed === "adaptive" || collapsed === "auto") return "adaptive";
|
|
35
|
+
if (collapsed === "max") return "max";
|
|
36
|
+
if (collapsed === "ultra") return "ultra";
|
|
37
|
+
if (collapsed === "xhigh" || collapsed === "extrahigh") return "xhigh";
|
|
38
|
+
if (key === "off") return "off";
|
|
39
|
+
if (["on", "enable", "enabled"].includes(key)) return "low";
|
|
40
|
+
if (["min", "minimal"].includes(key)) return "minimal";
|
|
41
|
+
if (["low", "thinkhard", "think-hard", "think_hard"].includes(key)) {
|
|
42
|
+
return "low";
|
|
40
43
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
];
|
|
50
|
-
const match = candidates.find(isNormalizeThinkLevel);
|
|
51
|
-
if (!match) throw new Error("OpenClaw normalizeThinkLevel export not found");
|
|
52
|
-
return match;
|
|
44
|
+
if (["mid", "med", "medium", "thinkharder", "think-harder", "harder"].includes(key)) {
|
|
45
|
+
return "medium";
|
|
46
|
+
}
|
|
47
|
+
if (["high", "ultrathink", "thinkhardest", "highest"].includes(key)) {
|
|
48
|
+
return "high";
|
|
49
|
+
}
|
|
50
|
+
if (key === "think") return "minimal";
|
|
51
|
+
return null;
|
|
53
52
|
};
|
|
54
53
|
|
|
55
54
|
const splitModelKey = (modelKey = "") => {
|
|
@@ -80,13 +79,13 @@ const resolveThinkingApi = async () => {
|
|
|
80
79
|
return {
|
|
81
80
|
listThinkingLevelOptions: mod.listThinkingLevelOptions || mod.i,
|
|
82
81
|
resolveThinkingDefaultForModel: mod.resolveThinkingDefaultForModel || mod.s,
|
|
83
|
-
normalizeThinkLevel: resolveNormalizeThinkLevel(mod),
|
|
84
82
|
};
|
|
85
83
|
};
|
|
86
84
|
|
|
87
85
|
const resolveThinkingOptionsForModel = async ({
|
|
88
86
|
modelKey = "",
|
|
89
87
|
catalog = [],
|
|
88
|
+
agentRuntime = "",
|
|
90
89
|
} = {}) => {
|
|
91
90
|
const { provider, model } = splitModelKey(modelKey);
|
|
92
91
|
if (!provider || !model) {
|
|
@@ -96,12 +95,20 @@ const resolveThinkingOptionsForModel = async ({
|
|
|
96
95
|
};
|
|
97
96
|
}
|
|
98
97
|
const api = await resolveThinkingApi();
|
|
99
|
-
const
|
|
98
|
+
const normalizedAgentRuntime = String(agentRuntime || "").trim() || undefined;
|
|
99
|
+
const levels =
|
|
100
|
+
api.listThinkingLevelOptions(
|
|
101
|
+
provider,
|
|
102
|
+
model,
|
|
103
|
+
catalog,
|
|
104
|
+
normalizedAgentRuntime,
|
|
105
|
+
) || [];
|
|
100
106
|
const modelDefault =
|
|
101
107
|
api.resolveThinkingDefaultForModel({
|
|
102
108
|
provider,
|
|
103
109
|
model,
|
|
104
110
|
catalog,
|
|
111
|
+
agentRuntime: normalizedAgentRuntime,
|
|
105
112
|
}) || "off";
|
|
106
113
|
return {
|
|
107
114
|
levels: levels.map((entry) => ({
|
|
@@ -114,14 +121,13 @@ const resolveThinkingOptionsForModel = async ({
|
|
|
114
121
|
|
|
115
122
|
const normalizeThinkingDefaultValue = async (raw) => {
|
|
116
123
|
if (raw === null || raw === undefined || raw === "") return null;
|
|
117
|
-
|
|
118
|
-
const normalized = api.normalizeThinkLevel(String(raw || "").trim());
|
|
119
|
-
return normalized || null;
|
|
124
|
+
return normalizeThinkingLevel(raw);
|
|
120
125
|
};
|
|
121
126
|
|
|
122
127
|
module.exports = {
|
|
123
128
|
buildCatalogEntry,
|
|
124
129
|
loadThinkingModule,
|
|
130
|
+
normalizeThinkingLevel,
|
|
125
131
|
normalizeThinkingDefaultValue,
|
|
126
132
|
resolveThinkingOptionsForModel,
|
|
127
133
|
splitModelKey,
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
} = require("./constants");
|
|
11
11
|
const { normalizeOpenclawVersion } = require("./helpers");
|
|
12
12
|
const { parseJsonObjectFromNoisyOutput } = require("./utils/json");
|
|
13
|
+
const { assertSupportedNodeVersion } = require("../node-runtime");
|
|
13
14
|
|
|
14
15
|
const createOpenclawVersionService = ({
|
|
15
16
|
gatewayEnv,
|
|
@@ -124,6 +125,12 @@ const createOpenclawVersionService = ({
|
|
|
124
125
|
// Copying individual files (cp -af) avoids the rename syscall entirely.
|
|
125
126
|
const installLatestOpenclaw = () =>
|
|
126
127
|
new Promise((resolve, reject) => {
|
|
128
|
+
try {
|
|
129
|
+
assertSupportedNodeVersion();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
reject(error);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
127
134
|
const installDir = findInstallDir();
|
|
128
135
|
const tmpDir = fs.mkdtempSync(
|
|
129
136
|
path.join(os.tmpdir(), "openclaw-update-"),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const ensurePluginsShell = (cfg = {}) => {
|
|
2
|
+
if (!cfg.plugins || typeof cfg.plugins !== "object") cfg.plugins = {};
|
|
3
|
+
if (!Array.isArray(cfg.plugins.allow)) cfg.plugins.allow = [];
|
|
4
|
+
if (!cfg.plugins.load || typeof cfg.plugins.load !== "object") {
|
|
5
|
+
cfg.plugins.load = {};
|
|
6
|
+
}
|
|
7
|
+
if (!Array.isArray(cfg.plugins.load.paths)) cfg.plugins.load.paths = [];
|
|
8
|
+
if (!cfg.plugins.entries || typeof cfg.plugins.entries !== "object") {
|
|
9
|
+
cfg.plugins.entries = {};
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const ensurePluginAllowed = ({ cfg = {}, pluginKey = "" }) => {
|
|
14
|
+
const normalizedPluginKey = String(pluginKey || "").trim();
|
|
15
|
+
if (!normalizedPluginKey) return;
|
|
16
|
+
ensurePluginsShell(cfg);
|
|
17
|
+
if (!cfg.plugins.allow.includes(normalizedPluginKey)) {
|
|
18
|
+
cfg.plugins.allow.push(normalizedPluginKey);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
module.exports = { ensurePluginsShell, ensurePluginAllowed };
|
|
@@ -197,8 +197,12 @@ const registerModelRoutes = ({
|
|
|
197
197
|
typeof cfg.agents?.defaults?.thinkingDefault === "string"
|
|
198
198
|
? cfg.agents.defaults.thinkingDefault.trim()
|
|
199
199
|
: null;
|
|
200
|
+
const agentRuntime = String(
|
|
201
|
+
cfg.agents?.defaults?.models?.[modelKey]?.agentRuntime?.id || "",
|
|
202
|
+
).trim();
|
|
200
203
|
const { levels, modelDefault } = await resolveThinkingOptionsForModel({
|
|
201
204
|
modelKey,
|
|
205
|
+
agentRuntime,
|
|
202
206
|
});
|
|
203
207
|
const inheritedDefault = globalThinkingDefault || modelDefault || "off";
|
|
204
208
|
return res.json({
|
|
@@ -95,6 +95,28 @@ const registerOnboardingRoutes = ({
|
|
|
95
95
|
getBaseUrl,
|
|
96
96
|
runOnboardedBootSequence,
|
|
97
97
|
}) => {
|
|
98
|
+
const kOnboardingProgressMessages = {
|
|
99
|
+
creating_repo: "Creating repo...",
|
|
100
|
+
running_openclaw_onboard: "Running openclaw onboard...",
|
|
101
|
+
initial_git_push: "Initial git commit/push...",
|
|
102
|
+
starting_gateway: "Starting gateway...",
|
|
103
|
+
};
|
|
104
|
+
let onboardingProgress = {
|
|
105
|
+
active: false,
|
|
106
|
+
stage: "idle",
|
|
107
|
+
message: "",
|
|
108
|
+
updatedAt: null,
|
|
109
|
+
};
|
|
110
|
+
const setOnboardingProgress = (stage, { active = true } = {}) => {
|
|
111
|
+
const normalizedStage = String(stage || "").trim();
|
|
112
|
+
onboardingProgress = {
|
|
113
|
+
active,
|
|
114
|
+
stage: normalizedStage,
|
|
115
|
+
message: kOnboardingProgressMessages[normalizedStage] || "",
|
|
116
|
+
updatedAt: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
98
120
|
// Keep mutating onboarding routes marker-gated so in-progress imports
|
|
99
121
|
// can promote files before the final completion marker is written.
|
|
100
122
|
const hasExplicitOnboardingMarker = () =>
|
|
@@ -157,21 +179,32 @@ const registerOnboardingRoutes = ({
|
|
|
157
179
|
res.json({ onboarded: hasExplicitOnboardingMarker() });
|
|
158
180
|
});
|
|
159
181
|
|
|
182
|
+
app.get("/api/onboard/progress", (req, res) => {
|
|
183
|
+
res.json(onboardingProgress);
|
|
184
|
+
});
|
|
185
|
+
|
|
160
186
|
app.post("/api/onboard", async (req, res) => {
|
|
161
187
|
if (hasExplicitOnboardingMarker())
|
|
162
188
|
return res.json({ ok: false, error: "Already onboarded" });
|
|
163
189
|
|
|
164
190
|
try {
|
|
191
|
+
setOnboardingProgress("creating_repo");
|
|
165
192
|
const { vars, modelKey, importMode } = req.body;
|
|
166
193
|
const result = await onboardingService.completeOnboarding({
|
|
167
194
|
req,
|
|
168
195
|
vars,
|
|
169
196
|
modelKey,
|
|
170
197
|
importMode: !!importMode,
|
|
198
|
+
onProgress: (stage) => setOnboardingProgress(stage),
|
|
171
199
|
});
|
|
200
|
+
setOnboardingProgress(
|
|
201
|
+
result.body?.ok ? "starting_gateway" : "failed",
|
|
202
|
+
{ active: false },
|
|
203
|
+
);
|
|
172
204
|
res.status(result.status).json(result.body);
|
|
173
205
|
} catch (err) {
|
|
174
206
|
console.error("[onboard] Error:", err);
|
|
207
|
+
setOnboardingProgress("failed", { active: false });
|
|
175
208
|
res.status(500).json({ ok: false, error: sanitizeOnboardingError(err) });
|
|
176
209
|
}
|
|
177
210
|
});
|
|
@@ -3,6 +3,8 @@ const { readOpenclawConfig, writeOpenclawConfig } = require("./openclaw-config")
|
|
|
3
3
|
const {
|
|
4
4
|
migrateLegacyTelegramStreamingConfig,
|
|
5
5
|
} = require("./openclaw-config-migrations");
|
|
6
|
+
const { ensureCodexRuntimePlugin } = require("./codex-runtime-config");
|
|
7
|
+
const { ensurePluginsShell, ensurePluginAllowed } = require("./plugin-config");
|
|
6
8
|
|
|
7
9
|
const kUsageTrackerPluginPath = path.resolve(
|
|
8
10
|
__dirname,
|
|
@@ -14,27 +16,6 @@ const kConversationAccessHookPolicyKey = "allowConversationAccess";
|
|
|
14
16
|
const kChannelPluginIds = ["telegram", "discord", "slack", "whatsapp"];
|
|
15
17
|
const kDefaultDiscordGroupPolicy = "disabled";
|
|
16
18
|
|
|
17
|
-
const ensurePluginsShell = (cfg = {}) => {
|
|
18
|
-
if (!cfg.plugins || typeof cfg.plugins !== "object") cfg.plugins = {};
|
|
19
|
-
if (!Array.isArray(cfg.plugins.allow)) cfg.plugins.allow = [];
|
|
20
|
-
if (!cfg.plugins.load || typeof cfg.plugins.load !== "object") {
|
|
21
|
-
cfg.plugins.load = {};
|
|
22
|
-
}
|
|
23
|
-
if (!Array.isArray(cfg.plugins.load.paths)) cfg.plugins.load.paths = [];
|
|
24
|
-
if (!cfg.plugins.entries || typeof cfg.plugins.entries !== "object") {
|
|
25
|
-
cfg.plugins.entries = {};
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const ensurePluginAllowed = ({ cfg = {}, pluginKey = "" }) => {
|
|
30
|
-
const normalizedPluginKey = String(pluginKey || "").trim();
|
|
31
|
-
if (!normalizedPluginKey) return;
|
|
32
|
-
ensurePluginsShell(cfg);
|
|
33
|
-
if (!cfg.plugins.allow.includes(normalizedPluginKey)) {
|
|
34
|
-
cfg.plugins.allow.push(normalizedPluginKey);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
19
|
const buildUsageTrackerHookPolicy = ({ existingHooks = {} } = {}) => {
|
|
39
20
|
const hooks = {};
|
|
40
21
|
if (typeof existingHooks.allowPromptInjection === "boolean") {
|
|
@@ -115,6 +96,7 @@ const reconcileEnabledChannelPlugins = (cfg = {}) => {
|
|
|
115
96
|
|
|
116
97
|
const reconcileManagedPluginConfig = (cfg = {}) => {
|
|
117
98
|
let changed = ensureUsageTrackerPluginEntry(cfg);
|
|
99
|
+
if (ensureCodexRuntimePlugin(cfg)) changed = true;
|
|
118
100
|
if (reconcileEnabledChannelPlugins(cfg)) changed = true;
|
|
119
101
|
if (reconcileDiscordGroupPolicy(cfg)) changed = true;
|
|
120
102
|
return changed;
|
package/lib/server/watchdog.js
CHANGED
|
@@ -63,6 +63,7 @@ const createWatchdog = ({
|
|
|
63
63
|
pendingRecoveryNoticeSource: "",
|
|
64
64
|
awaitingAutoRepairRecovery: false,
|
|
65
65
|
startupConsecutiveHealthFailures: 0,
|
|
66
|
+
configurationErrorActive: false,
|
|
66
67
|
};
|
|
67
68
|
let healthTimer = null;
|
|
68
69
|
let bootstrapHealthTimer = null;
|
|
@@ -90,6 +91,7 @@ const createWatchdog = ({
|
|
|
90
91
|
|
|
91
92
|
const scheduleDegradedHealthCheck = () => {
|
|
92
93
|
if (degradedHealthTimer) return;
|
|
94
|
+
if (state.configurationErrorActive) return;
|
|
93
95
|
if (state.health !== "degraded" || state.lifecycle !== "running") return;
|
|
94
96
|
degradedHealthTimer = setTimeout(async () => {
|
|
95
97
|
degradedHealthTimer = null;
|
|
@@ -380,6 +382,9 @@ const createWatchdog = ({
|
|
|
380
382
|
};
|
|
381
383
|
|
|
382
384
|
const runRepair = async ({ source, correlationId, force = false }) => {
|
|
385
|
+
if (state.configurationErrorActive && !force) {
|
|
386
|
+
return { ok: false, skipped: true, reason: "configuration_error" };
|
|
387
|
+
}
|
|
383
388
|
if (!force && !state.autoRepair) {
|
|
384
389
|
return { ok: false, skipped: true, reason: "auto_repair_disabled" };
|
|
385
390
|
}
|
|
@@ -389,10 +394,16 @@ const createWatchdog = ({
|
|
|
389
394
|
if (state.operationInProgress) {
|
|
390
395
|
return { ok: false, skipped: true, reason: "operation_in_progress" };
|
|
391
396
|
}
|
|
397
|
+
if (force) {
|
|
398
|
+
state.configurationErrorActive = false;
|
|
399
|
+
}
|
|
392
400
|
|
|
393
401
|
state.operationInProgress = true;
|
|
394
402
|
try {
|
|
395
403
|
const result = await clawCmd("doctor --fix --yes", { quiet: true });
|
|
404
|
+
if (state.configurationErrorActive && !force) {
|
|
405
|
+
return { ok: false, skipped: true, reason: "configuration_error" };
|
|
406
|
+
}
|
|
396
407
|
const ok = !!result?.ok;
|
|
397
408
|
logEvent("repair", source, ok ? "ok" : "failed", result, correlationId);
|
|
398
409
|
if (ok) {
|
|
@@ -484,6 +495,7 @@ const createWatchdog = ({
|
|
|
484
495
|
source = "health_timer",
|
|
485
496
|
allowAutoRepair = true,
|
|
486
497
|
} = {}) => {
|
|
498
|
+
if (state.configurationErrorActive) return false;
|
|
487
499
|
if (
|
|
488
500
|
state.expectedRestartInProgress &&
|
|
489
501
|
Date.now() >= state.expectedRestartUntilMs
|
|
@@ -495,6 +507,9 @@ const createWatchdog = ({
|
|
|
495
507
|
const correlationId = createCorrelationId();
|
|
496
508
|
state.lastHealthCheckAt = new Date().toISOString();
|
|
497
509
|
const parsed = await probeGatewayHealth();
|
|
510
|
+
// The gateway may exit with EX_CONFIG while a probe is in flight. Keep the
|
|
511
|
+
// latched configuration-error state from being overwritten by that result.
|
|
512
|
+
if (state.configurationErrorActive) return false;
|
|
498
513
|
const staleAfterRestart =
|
|
499
514
|
gatewayStartedAtAtStart != null &&
|
|
500
515
|
state.gatewayStartedAt != null &&
|
|
@@ -727,6 +742,33 @@ const createWatchdog = ({
|
|
|
727
742
|
return;
|
|
728
743
|
}
|
|
729
744
|
|
|
745
|
+
if (code === 78) {
|
|
746
|
+
state.configurationErrorActive = true;
|
|
747
|
+
state.lifecycle = "configuration_error";
|
|
748
|
+
state.health = "unhealthy";
|
|
749
|
+
state.uptimeStartedAt = null;
|
|
750
|
+
state.crashRecoveryActive = false;
|
|
751
|
+
state.startupConsecutiveHealthFailures = 0;
|
|
752
|
+
logEvent(
|
|
753
|
+
"config_error",
|
|
754
|
+
"exit_event",
|
|
755
|
+
"failed",
|
|
756
|
+
{ code, signal: signal ?? null, stderrTail },
|
|
757
|
+
correlationId,
|
|
758
|
+
);
|
|
759
|
+
void notifyOncePerIncident(
|
|
760
|
+
"gateway_config_error",
|
|
761
|
+
[
|
|
762
|
+
"🐺 *AlphaClaw Watchdog*",
|
|
763
|
+
withViewLogsSuffix("🔴 Gateway configuration invalid"),
|
|
764
|
+
"OpenClaw stopped with `EX_CONFIG`; automatic restart is paused.",
|
|
765
|
+
].join("\n"),
|
|
766
|
+
correlationId,
|
|
767
|
+
"config_error",
|
|
768
|
+
);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
730
772
|
state.lifecycle = "crashed";
|
|
731
773
|
state.health = "unhealthy";
|
|
732
774
|
state.uptimeStartedAt = null;
|
|
@@ -787,6 +829,7 @@ const createWatchdog = ({
|
|
|
787
829
|
|
|
788
830
|
const onGatewayLaunch = ({ startedAt = Date.now(), pid = null } = {}) => {
|
|
789
831
|
clearDegradedHealthCheckTimer();
|
|
832
|
+
state.configurationErrorActive = false;
|
|
790
833
|
state.lifecycle = "running";
|
|
791
834
|
state.health = "unknown";
|
|
792
835
|
state.startupConsecutiveHealthFailures = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrysb/alphaclaw",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.30",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"express": "^4.21.0",
|
|
35
35
|
"http-proxy": "^1.18.1",
|
|
36
|
-
"openclaw": "2026.
|
|
36
|
+
"openclaw": "2026.7.1",
|
|
37
37
|
"ws": "^8.19.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
@@ -51,6 +51,6 @@
|
|
|
51
51
|
"wouter-preact": "^3.7.1"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
|
-
"node": ">=22.
|
|
54
|
+
"node": ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0"
|
|
55
55
|
}
|
|
56
56
|
}
|