@chrysb/alphaclaw 0.9.9 → 0.9.11
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/lib/public/dist/app.bundle.js +1773 -1747
- package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +4 -5
- package/lib/public/js/components/agents-tab/agent-pairing-section.js +11 -5
- package/lib/public/js/components/cron-tab/cron-helpers.js +13 -1
- package/lib/public/js/components/cron-tab/use-cron-tab.js +4 -3
- package/lib/public/js/components/general/index.js +6 -1
- package/lib/public/js/components/general/use-general-tab.js +17 -20
- package/lib/public/js/components/models-tab/index.js +5 -1
- package/lib/public/js/components/models-tab/model-picker.js +52 -0
- package/lib/public/js/components/onboarding/use-welcome-pairing.js +4 -1
- package/lib/public/js/components/pairings.js +75 -4
- package/lib/public/js/components/welcome/use-welcome.js +37 -8
- package/lib/public/js/hooks/usePolling.js +46 -13
- package/lib/public/js/lib/model-config.js +6 -2
- package/lib/server/agents/channels.js +53 -9
- package/lib/server/commands.js +4 -1
- package/lib/server/constants.js +14 -3
- package/lib/server/cost-utils.js +9 -0
- package/lib/server/cron-service.js +12 -1
- package/lib/server/db/doctor/index.js +9 -0
- package/lib/server/db/usage/index.js +13 -0
- package/lib/server/db/watchdog/index.js +13 -1
- package/lib/server/db/webhooks/index.js +13 -1
- package/lib/server/gateway.js +119 -8
- package/lib/server/init/register-server-routes.js +3 -0
- package/lib/server/init/runtime-init.js +2 -0
- package/lib/server/internal-files-migration.js +11 -1
- package/lib/server/model-catalog-bootstrap.json +3193 -0
- package/lib/server/model-catalog-cache.js +124 -32
- package/lib/server/onboarding/github.js +79 -2
- package/lib/server/onboarding/index.js +2 -9
- package/lib/server/onboarding/openclaw.js +18 -4
- package/lib/server/openclaw-runtime-env.js +55 -0
- package/lib/server/openclaw-version.js +2 -1
- package/lib/server/routes/models.js +28 -0
- package/lib/server/routes/pairings.js +106 -15
- package/lib/server/usage-tracker-config.js +28 -3
- package/lib/server/utils/command-output.js +11 -0
- package/lib/server.js +4 -0
- package/lib/setup/gitignore +2 -0
- package/package.json +2 -2
- package/patches/openclaw+2026.4.23.patch +63 -0
- package/patches/openclaw+2026.4.15.patch +0 -13
|
@@ -9,6 +9,10 @@ export const getAuthProviderFromModelProvider = (provider) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const kFeaturedModelDefs = [
|
|
12
|
+
{
|
|
13
|
+
label: "Opus 4.7",
|
|
14
|
+
preferredKeys: ["anthropic/claude-opus-4-7"],
|
|
15
|
+
},
|
|
12
16
|
{
|
|
13
17
|
label: "Opus 4.6",
|
|
14
18
|
preferredKeys: ["anthropic/claude-opus-4-6"],
|
|
@@ -22,8 +26,8 @@ export const kFeaturedModelDefs = [
|
|
|
22
26
|
preferredKeys: ["openai-codex/gpt-5.3-codex"],
|
|
23
27
|
},
|
|
24
28
|
{
|
|
25
|
-
label: "GPT-5.
|
|
26
|
-
preferredKeys: ["openai-codex/gpt-5.
|
|
29
|
+
label: "GPT-5.5",
|
|
30
|
+
preferredKeys: ["openai-codex/gpt-5.5"],
|
|
27
31
|
},
|
|
28
32
|
{
|
|
29
33
|
label: "Gemini 3.1 Pro",
|
|
@@ -38,6 +38,41 @@ const createChannelsDomain = ({
|
|
|
38
38
|
}) => {
|
|
39
39
|
let createChannelAccountInProgress = false;
|
|
40
40
|
|
|
41
|
+
const formatClawResultOutput = (result) =>
|
|
42
|
+
[result?.stderr, result?.stdout].filter(Boolean).join("\n").trim();
|
|
43
|
+
|
|
44
|
+
const isConfigMutationConflictResult = (result) =>
|
|
45
|
+
/ConfigMutationConflictError|config changed since last load/i.test(
|
|
46
|
+
formatClawResultOutput(result),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const waitForRetry = (delayMs) =>
|
|
50
|
+
new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
51
|
+
|
|
52
|
+
const clawCmdWithConfigConflictRetry = async (
|
|
53
|
+
command,
|
|
54
|
+
options,
|
|
55
|
+
{ label = "command", delaysMs = [250, 750] } = {},
|
|
56
|
+
) => {
|
|
57
|
+
for (let attempt = 0; attempt <= delaysMs.length; attempt += 1) {
|
|
58
|
+
const result = await clawCmd(command, options);
|
|
59
|
+
if (result?.ok || !isConfigMutationConflictResult(result)) {
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
if (attempt >= delaysMs.length) {
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
const delayMs = Number(delaysMs[attempt] || 0);
|
|
66
|
+
console.warn(
|
|
67
|
+
`[alphaclaw] Retrying openclaw ${label} after config mutation conflict`,
|
|
68
|
+
);
|
|
69
|
+
if (delayMs > 0) {
|
|
70
|
+
await waitForRetry(delayMs);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { ok: false, stdout: "", stderr: "Command retry exhausted" };
|
|
74
|
+
};
|
|
75
|
+
|
|
41
76
|
const getChannelAccountToken = ({
|
|
42
77
|
provider: rawProvider,
|
|
43
78
|
accountId: rawAccountId,
|
|
@@ -266,10 +301,14 @@ const createChannelsDomain = ({
|
|
|
266
301
|
? `--app-token ${shellEscapeArg(appToken)}`
|
|
267
302
|
: "",
|
|
268
303
|
].filter(Boolean);
|
|
269
|
-
const addResult = await
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
304
|
+
const addResult = await clawCmdWithConfigConflictRetry(
|
|
305
|
+
addArgs.join(" "),
|
|
306
|
+
{
|
|
307
|
+
quiet: true,
|
|
308
|
+
timeoutMs: 30000,
|
|
309
|
+
},
|
|
310
|
+
{ label: "channels add" },
|
|
311
|
+
);
|
|
273
312
|
if (!addResult?.ok) {
|
|
274
313
|
throw new Error(
|
|
275
314
|
addResult?.stderr ||
|
|
@@ -323,9 +362,10 @@ const createChannelsDomain = ({
|
|
|
323
362
|
saveConfig({ fsImpl, OPENCLAW_DIR, config: nextCfg });
|
|
324
363
|
onProgress({ phase: "binding", label: "Binding agent..." });
|
|
325
364
|
const bindSpec = buildBindingSpec({ provider, accountId });
|
|
326
|
-
const bindResult = await
|
|
365
|
+
const bindResult = await clawCmdWithConfigConflictRetry(
|
|
327
366
|
`agents bind --agent ${shellEscapeArg(agentId)} --bind ${shellEscapeArg(bindSpec)}`,
|
|
328
367
|
{ quiet: true, timeoutMs: 30000 },
|
|
368
|
+
{ label: "agents bind" },
|
|
329
369
|
);
|
|
330
370
|
if (!bindResult?.ok) {
|
|
331
371
|
throw new Error(
|
|
@@ -885,10 +925,14 @@ const createChannelsDomain = ({
|
|
|
885
925
|
name ? `--name ${shellEscapeArg(name)}` : "",
|
|
886
926
|
`--token ${shellEscapeArg(ownerNumber)}`,
|
|
887
927
|
].filter(Boolean);
|
|
888
|
-
const addResult = await
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
928
|
+
const addResult = await clawCmdWithConfigConflictRetry(
|
|
929
|
+
addArgs.join(" "),
|
|
930
|
+
{
|
|
931
|
+
quiet: true,
|
|
932
|
+
timeoutMs: 30000,
|
|
933
|
+
},
|
|
934
|
+
{ label: "channels add" },
|
|
935
|
+
);
|
|
892
936
|
if (!addResult?.ok) {
|
|
893
937
|
throw new Error(
|
|
894
938
|
addResult?.stderr ||
|
package/lib/server/commands.js
CHANGED
|
@@ -20,8 +20,11 @@ const createCommands = ({ gatewayEnv }) => {
|
|
|
20
20
|
);
|
|
21
21
|
exec(cmd, { timeout: timeoutMs, ...execOpts }, (err, stdout, stderr) => {
|
|
22
22
|
if (err) {
|
|
23
|
+
err.stdout = String(stdout || "").trim();
|
|
24
|
+
err.stderr = String(stderr || "").trim();
|
|
25
|
+
err.cmd = cmd;
|
|
23
26
|
console.error(
|
|
24
|
-
`[onboard] Error: ${(stderr || err.message).slice(0, 300)}`,
|
|
27
|
+
`[onboard] Error: ${String(stderr || err.message || "").slice(0, 300)}`,
|
|
25
28
|
);
|
|
26
29
|
return reject(err);
|
|
27
30
|
}
|
package/lib/server/constants.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const os = require("os");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const kBrowseFilePolicies = require("../public/shared/browse-file-policies.json");
|
|
4
|
+
const kBootstrapModelCatalog = require("./model-catalog-bootstrap.json");
|
|
4
5
|
const { parsePositiveInt } = require("./utils/number");
|
|
5
6
|
|
|
6
7
|
// Portable root directory: --root-dir flag sets ALPHACLAW_ROOT_DIR before require
|
|
@@ -90,7 +91,12 @@ const kOnboardingModelProviders = new Set([
|
|
|
90
91
|
"groq",
|
|
91
92
|
"vllm",
|
|
92
93
|
]);
|
|
93
|
-
const
|
|
94
|
+
const kMinimalFallbackOnboardingModels = [
|
|
95
|
+
{
|
|
96
|
+
key: "anthropic/claude-opus-4-7",
|
|
97
|
+
provider: "anthropic",
|
|
98
|
+
label: "Claude Opus 4.7",
|
|
99
|
+
},
|
|
94
100
|
{
|
|
95
101
|
key: "anthropic/claude-opus-4-6",
|
|
96
102
|
provider: "anthropic",
|
|
@@ -107,9 +113,9 @@ const kFallbackOnboardingModels = [
|
|
|
107
113
|
label: "Claude Haiku 4.6",
|
|
108
114
|
},
|
|
109
115
|
{
|
|
110
|
-
key: "openai-codex/gpt-5.
|
|
116
|
+
key: "openai-codex/gpt-5.5",
|
|
111
117
|
provider: "openai-codex",
|
|
112
|
-
label: "GPT-5.
|
|
118
|
+
label: "GPT-5.5",
|
|
113
119
|
},
|
|
114
120
|
{
|
|
115
121
|
key: "openai-codex/gpt-5.3-codex",
|
|
@@ -132,6 +138,11 @@ const kFallbackOnboardingModels = [
|
|
|
132
138
|
label: "Gemini 3 Flash Preview",
|
|
133
139
|
},
|
|
134
140
|
];
|
|
141
|
+
const kFallbackOnboardingModels =
|
|
142
|
+
Array.isArray(kBootstrapModelCatalog.models) &&
|
|
143
|
+
kBootstrapModelCatalog.models.length > 0
|
|
144
|
+
? kBootstrapModelCatalog.models
|
|
145
|
+
: kMinimalFallbackOnboardingModels;
|
|
135
146
|
|
|
136
147
|
const kVersionCacheTtlMs = 60 * 1000;
|
|
137
148
|
const kLatestVersionCacheTtlMs = 10 * 60 * 1000;
|
package/lib/server/cost-utils.js
CHANGED
|
@@ -5,7 +5,16 @@ const kTokensPerMillion = 1_000_000;
|
|
|
5
5
|
const kLongContextThresholdTokens = 200_000;
|
|
6
6
|
const kNodeModulesPricingCacheTtlMs = 60_000;
|
|
7
7
|
|
|
8
|
+
const kClaudeOpus47Pricing = {
|
|
9
|
+
input: 5.0,
|
|
10
|
+
output: 25.0,
|
|
11
|
+
cacheRead: 0.5,
|
|
12
|
+
cacheWrite: 6.25,
|
|
13
|
+
};
|
|
14
|
+
|
|
8
15
|
const kGlobalModelPricing = {
|
|
16
|
+
"claude-opus-4-7": kClaudeOpus47Pricing,
|
|
17
|
+
"claude-opus-4.7": kClaudeOpus47Pricing,
|
|
9
18
|
"claude-opus-4-6": {
|
|
10
19
|
input: (tokens) => (tokens > kLongContextThresholdTokens ? 10.0 : 5.0),
|
|
11
20
|
output: (tokens) => (tokens > kLongContextThresholdTokens ? 37.5 : 25.0),
|
|
@@ -485,6 +485,16 @@ const parseCommandJson = (rawOutput) => {
|
|
|
485
485
|
return null;
|
|
486
486
|
};
|
|
487
487
|
|
|
488
|
+
const resolvePromptEditFlag = ({ cronDir, jobId }) => {
|
|
489
|
+
const store = readCronStore({ cronDir });
|
|
490
|
+
const job = store.jobs.find((entry) => String(entry?.id || "") === jobId);
|
|
491
|
+
if (!job) throw new Error(`unknown cron job id: ${jobId}`);
|
|
492
|
+
const payloadKind = String(job?.payload?.kind || "").trim();
|
|
493
|
+
if (payloadKind === "systemEvent") return "--system-event";
|
|
494
|
+
if (payloadKind === "agentTurn") return "--message";
|
|
495
|
+
throw new Error(`unsupported cron payload kind: ${payloadKind || "unknown"}`);
|
|
496
|
+
};
|
|
497
|
+
|
|
488
498
|
const createCronService = ({
|
|
489
499
|
clawCmd,
|
|
490
500
|
OPENCLAW_DIR,
|
|
@@ -548,7 +558,8 @@ const createCronService = ({
|
|
|
548
558
|
|
|
549
559
|
const updateJobPrompt = async ({ jobId, message }) => {
|
|
550
560
|
const safeJobId = sanitizeCronJobId(jobId);
|
|
551
|
-
const
|
|
561
|
+
const promptFlag = resolvePromptEditFlag({ cronDir, jobId: safeJobId });
|
|
562
|
+
const command = `cron edit ${shellEscapeArg(safeJobId)} ${promptFlag} ${shellEscapeArg(message || "")}`;
|
|
552
563
|
return runCommand(command, { timeoutMs: 60000 });
|
|
553
564
|
};
|
|
554
565
|
|
|
@@ -18,6 +18,13 @@ const ensureDb = () => {
|
|
|
18
18
|
return db;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
const closeDoctorDb = () => {
|
|
22
|
+
if (!db) return;
|
|
23
|
+
const database = db;
|
|
24
|
+
db = null;
|
|
25
|
+
database.close();
|
|
26
|
+
};
|
|
27
|
+
|
|
21
28
|
const parseJsonText = (value, fallbackValue) => {
|
|
22
29
|
if (typeof value !== "string" || !value) return fallbackValue;
|
|
23
30
|
try {
|
|
@@ -171,6 +178,7 @@ const toRunModel = (row) => {
|
|
|
171
178
|
};
|
|
172
179
|
|
|
173
180
|
const initDoctorDb = ({ rootDir }) => {
|
|
181
|
+
closeDoctorDb();
|
|
174
182
|
const dbDir = path.join(rootDir, "db");
|
|
175
183
|
fs.mkdirSync(dbDir, { recursive: true });
|
|
176
184
|
const dbPath = path.join(dbDir, "doctor.db");
|
|
@@ -511,6 +519,7 @@ const updateDoctorCardStatus = ({ id, status }) => {
|
|
|
511
519
|
|
|
512
520
|
module.exports = {
|
|
513
521
|
initDoctorDb,
|
|
522
|
+
closeDoctorDb,
|
|
514
523
|
markIncompleteRunsFailed,
|
|
515
524
|
getDoctorMeta,
|
|
516
525
|
setDoctorMeta,
|
|
@@ -15,7 +15,19 @@ const ensureDb = () => {
|
|
|
15
15
|
return db;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
const closeUsageDb = () => {
|
|
19
|
+
if (!db) {
|
|
20
|
+
usageDbPath = "";
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const database = db;
|
|
24
|
+
db = null;
|
|
25
|
+
usageDbPath = "";
|
|
26
|
+
database.close();
|
|
27
|
+
};
|
|
28
|
+
|
|
18
29
|
const initUsageDb = ({ rootDir }) => {
|
|
30
|
+
closeUsageDb();
|
|
19
31
|
const dbDir = path.join(rootDir, "db");
|
|
20
32
|
fs.mkdirSync(dbDir, { recursive: true });
|
|
21
33
|
usageDbPath = path.join(dbDir, "usage.db");
|
|
@@ -155,6 +167,7 @@ const getSessionUsageByKeyPattern = ({ keyPattern = "", sinceMs = 0 } = {}) => {
|
|
|
155
167
|
|
|
156
168
|
module.exports = {
|
|
157
169
|
initUsageDb,
|
|
170
|
+
closeUsageDb,
|
|
158
171
|
getDailySummary: (options = {}) => getDailySummary({ database: ensureDb(), ...options }),
|
|
159
172
|
getSessionsList: (options = {}) => getSessionsList({ database: ensureDb(), ...options }),
|
|
160
173
|
getSessionDetail: (options = {}) => getSessionDetail({ database: ensureDb(), ...options }),
|
|
@@ -15,14 +15,25 @@ const ensureDb = () => {
|
|
|
15
15
|
return db;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
const closeWatchdogDb = () => {
|
|
19
|
+
if (pruneTimer) {
|
|
20
|
+
clearInterval(pruneTimer);
|
|
21
|
+
pruneTimer = null;
|
|
22
|
+
}
|
|
23
|
+
if (!db) return;
|
|
24
|
+
const database = db;
|
|
25
|
+
db = null;
|
|
26
|
+
database.close();
|
|
27
|
+
};
|
|
28
|
+
|
|
18
29
|
const initWatchdogDb = ({ rootDir, pruneDays = 30 }) => {
|
|
30
|
+
closeWatchdogDb();
|
|
19
31
|
const dbDir = path.join(rootDir, "db");
|
|
20
32
|
fs.mkdirSync(dbDir, { recursive: true });
|
|
21
33
|
const dbPath = path.join(dbDir, "watchdog.db");
|
|
22
34
|
db = new DatabaseSync(dbPath);
|
|
23
35
|
createSchema(db);
|
|
24
36
|
pruneWatchdogEvents(pruneDays);
|
|
25
|
-
if (pruneTimer) clearInterval(pruneTimer);
|
|
26
37
|
pruneTimer = setInterval(() => {
|
|
27
38
|
try {
|
|
28
39
|
pruneWatchdogEvents(pruneDays);
|
|
@@ -125,6 +136,7 @@ const pruneWatchdogEvents = (days = 30) => {
|
|
|
125
136
|
|
|
126
137
|
module.exports = {
|
|
127
138
|
initWatchdogDb,
|
|
139
|
+
closeWatchdogDb,
|
|
128
140
|
insertWatchdogEvent,
|
|
129
141
|
getRecentEvents,
|
|
130
142
|
pruneWatchdogEvents,
|
|
@@ -17,14 +17,25 @@ const ensureDb = () => {
|
|
|
17
17
|
return db;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
const closeWebhooksDb = () => {
|
|
21
|
+
if (pruneTimer) {
|
|
22
|
+
clearInterval(pruneTimer);
|
|
23
|
+
pruneTimer = null;
|
|
24
|
+
}
|
|
25
|
+
if (!db) return;
|
|
26
|
+
const database = db;
|
|
27
|
+
db = null;
|
|
28
|
+
database.close();
|
|
29
|
+
};
|
|
30
|
+
|
|
20
31
|
const initWebhooksDb = ({ rootDir, pruneDays = 30 }) => {
|
|
32
|
+
closeWebhooksDb();
|
|
21
33
|
const dbDir = path.join(rootDir, "db");
|
|
22
34
|
fs.mkdirSync(dbDir, { recursive: true });
|
|
23
35
|
const dbPath = path.join(dbDir, "webhooks.db");
|
|
24
36
|
db = new DatabaseSync(dbPath);
|
|
25
37
|
createSchema(db);
|
|
26
38
|
pruneOldEntries(pruneDays);
|
|
27
|
-
if (pruneTimer) clearInterval(pruneTimer);
|
|
28
39
|
pruneTimer = setInterval(() => {
|
|
29
40
|
try {
|
|
30
41
|
pruneOldEntries(pruneDays);
|
|
@@ -413,6 +424,7 @@ const pruneOldEntries = (days = 30) => {
|
|
|
413
424
|
|
|
414
425
|
module.exports = {
|
|
415
426
|
initWebhooksDb,
|
|
427
|
+
closeWebhooksDb,
|
|
416
428
|
insertRequest,
|
|
417
429
|
getRequests,
|
|
418
430
|
getRequestById,
|
package/lib/server/gateway.js
CHANGED
|
@@ -11,11 +11,13 @@ const {
|
|
|
11
11
|
kOnboardingMarkerPath,
|
|
12
12
|
kRootDir,
|
|
13
13
|
} = require("./constants");
|
|
14
|
+
const { withOpenclawStartupEnv } = require("./openclaw-runtime-env");
|
|
14
15
|
|
|
15
16
|
let gatewayChild = null;
|
|
16
17
|
let gatewayExitHandler = null;
|
|
17
18
|
let gatewayLaunchHandler = null;
|
|
18
19
|
const kGatewayStderrTailLines = 50;
|
|
20
|
+
const kPluginRuntimeDepsPreflightTimeoutMs = 120 * 1000;
|
|
19
21
|
let gatewayStderrTail = [];
|
|
20
22
|
const expectedExitPids = new Set();
|
|
21
23
|
|
|
@@ -41,14 +43,117 @@ const setGatewayLaunchHandler = (handler) => {
|
|
|
41
43
|
gatewayLaunchHandler = typeof handler === "function" ? handler : null;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
|
-
const gatewayEnv = () =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
const gatewayEnv = () =>
|
|
47
|
+
withOpenclawStartupEnv({
|
|
48
|
+
...process.env,
|
|
49
|
+
HOME: kRootDir,
|
|
50
|
+
OPENCLAW_HOME: kRootDir,
|
|
51
|
+
OPENCLAW_CONFIG_PATH: `${OPENCLAW_DIR}/openclaw.json`,
|
|
52
|
+
OPENCLAW_STATE_DIR: OPENCLAW_DIR,
|
|
53
|
+
XDG_CONFIG_HOME: OPENCLAW_DIR,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const resolveOpenclawExtensionsDir = () => {
|
|
57
|
+
try {
|
|
58
|
+
const entryPath = require.resolve("openclaw");
|
|
59
|
+
const entryDir = path.dirname(entryPath);
|
|
60
|
+
const distDir =
|
|
61
|
+
path.basename(entryDir) === "dist" ? entryDir : path.join(entryDir, "dist");
|
|
62
|
+
return path.join(distDir, "extensions");
|
|
63
|
+
} catch {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const isOpenclawInstallStageDir = (name) =>
|
|
69
|
+
name === ".openclaw-install-stage" ||
|
|
70
|
+
String(name || "").startsWith(".openclaw-install-stage-");
|
|
71
|
+
|
|
72
|
+
const cleanupOpenclawPluginInstallStages = ({
|
|
73
|
+
extensionsDir = resolveOpenclawExtensionsDir(),
|
|
74
|
+
} = {}) => {
|
|
75
|
+
if (!extensionsDir) return 0;
|
|
76
|
+
let removed = 0;
|
|
77
|
+
try {
|
|
78
|
+
for (const entry of fs.readdirSync(extensionsDir, { withFileTypes: true })) {
|
|
79
|
+
if (!entry?.isDirectory?.()) continue;
|
|
80
|
+
const pluginDir = path.join(extensionsDir, entry.name);
|
|
81
|
+
for (const child of fs.readdirSync(pluginDir, { withFileTypes: true })) {
|
|
82
|
+
if (!child?.isDirectory?.() || !isOpenclawInstallStageDir(child.name)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const stageDir = path.join(pluginDir, child.name);
|
|
86
|
+
fs.rmSync(stageDir, {
|
|
87
|
+
recursive: true,
|
|
88
|
+
force: true,
|
|
89
|
+
maxRetries: 3,
|
|
90
|
+
retryDelay: 100,
|
|
91
|
+
});
|
|
92
|
+
removed += 1;
|
|
93
|
+
console.log(`[alphaclaw] Removed stale OpenClaw plugin install stage: ${stageDir}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.warn(
|
|
98
|
+
`[alphaclaw] Could not clean OpenClaw plugin install stages: ${err.message}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return removed;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const hasEnabledChannelConfig = () => {
|
|
105
|
+
try {
|
|
106
|
+
const configPath = `${OPENCLAW_DIR}/openclaw.json`;
|
|
107
|
+
if (!fs.existsSync(configPath)) return false;
|
|
108
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
109
|
+
const channels = cfg?.channels && typeof cfg.channels === "object" ? cfg.channels : {};
|
|
110
|
+
return Object.keys(kChannelDefs).some((channel) => channels?.[channel]?.enabled === true);
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const isInstallStageFailure = (err) =>
|
|
117
|
+
/ENOTEMPTY|openclaw-install-stage/i.test(
|
|
118
|
+
[
|
|
119
|
+
err?.message,
|
|
120
|
+
err?.stdout?.toString?.(),
|
|
121
|
+
err?.stderr?.toString?.(),
|
|
122
|
+
]
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
.join("\n"),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const runPluginRuntimeDepsPreflight = () =>
|
|
128
|
+
execSync("openclaw plugins list --json", {
|
|
129
|
+
env: gatewayEnv(),
|
|
130
|
+
timeout: kPluginRuntimeDepsPreflightTimeoutMs,
|
|
131
|
+
encoding: "utf8",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const prepareOpenclawChannelPlugins = () => {
|
|
135
|
+
if (!hasEnabledChannelConfig()) return;
|
|
136
|
+
cleanupOpenclawPluginInstallStages();
|
|
137
|
+
try {
|
|
138
|
+
runPluginRuntimeDepsPreflight();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (!isInstallStageFailure(err)) {
|
|
141
|
+
console.warn(
|
|
142
|
+
`[alphaclaw] OpenClaw plugin preflight failed: ${(err.stderr || err.message || "").toString().trim().slice(0, 300)}`,
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
cleanupOpenclawPluginInstallStages();
|
|
147
|
+
try {
|
|
148
|
+
runPluginRuntimeDepsPreflight();
|
|
149
|
+
console.log("[alphaclaw] OpenClaw plugin preflight recovered after cleaning install stage");
|
|
150
|
+
} catch (retryErr) {
|
|
151
|
+
console.warn(
|
|
152
|
+
`[alphaclaw] OpenClaw plugin preflight retry failed: ${(retryErr.stderr || retryErr.message || "").toString().trim().slice(0, 300)}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
52
157
|
|
|
53
158
|
const writeOnboardingMarker = (reason) => {
|
|
54
159
|
fs.mkdirSync(ALPHACLAW_DIR, { recursive: true });
|
|
@@ -123,6 +228,9 @@ const isGatewayRunning = () =>
|
|
|
123
228
|
const runGatewayCmd = (cmd) => {
|
|
124
229
|
console.log(`[alphaclaw] Running: openclaw gateway ${cmd}`);
|
|
125
230
|
try {
|
|
231
|
+
if (cmd === "--force" || cmd === "restart") {
|
|
232
|
+
prepareOpenclawChannelPlugins();
|
|
233
|
+
}
|
|
126
234
|
const out = execSync(`openclaw gateway ${cmd}`, {
|
|
127
235
|
env: gatewayEnv(),
|
|
128
236
|
timeout: 15000,
|
|
@@ -147,6 +255,7 @@ const launchGatewayProcess = () => {
|
|
|
147
255
|
);
|
|
148
256
|
return gatewayChild;
|
|
149
257
|
}
|
|
258
|
+
prepareOpenclawChannelPlugins();
|
|
150
259
|
gatewayStderrTail = [];
|
|
151
260
|
const child = spawn("openclaw", ["gateway", "run"], {
|
|
152
261
|
env: gatewayEnv(),
|
|
@@ -505,6 +614,8 @@ module.exports = {
|
|
|
505
614
|
isOnboarded,
|
|
506
615
|
isGatewayRunning,
|
|
507
616
|
launchGatewayProcess,
|
|
617
|
+
cleanupOpenclawPluginInstallStages,
|
|
618
|
+
prepareOpenclawChannelPlugins,
|
|
508
619
|
setGatewayExitHandler,
|
|
509
620
|
setGatewayLaunchHandler,
|
|
510
621
|
runGatewayCmd,
|
|
@@ -97,6 +97,9 @@ const registerServerRoutes = ({
|
|
|
97
97
|
gatewayEnv,
|
|
98
98
|
parseJsonFromNoisyOutput,
|
|
99
99
|
normalizeOnboardingModels,
|
|
100
|
+
readOpenclawVersion: (options) =>
|
|
101
|
+
openclawVersionService?.readOpenclawVersion(options),
|
|
102
|
+
isOnboarded,
|
|
100
103
|
authProfiles,
|
|
101
104
|
readEnvFile,
|
|
102
105
|
writeEnvFile,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const initializeServerRuntime = ({
|
|
2
2
|
fs,
|
|
3
3
|
constants,
|
|
4
|
+
ensureOpenclawStartupEnv,
|
|
4
5
|
startEnvWatcher,
|
|
5
6
|
attachGatewaySignalHandlers,
|
|
6
7
|
cleanupStaleImportTempDirs,
|
|
7
8
|
migrateManagedInternalFiles,
|
|
8
9
|
}) => {
|
|
10
|
+
ensureOpenclawStartupEnv?.({ fsModule: fs });
|
|
9
11
|
startEnvWatcher();
|
|
10
12
|
attachGatewaySignalHandlers();
|
|
11
13
|
cleanupStaleImportTempDirs();
|
|
@@ -9,6 +9,16 @@ const kOpenclawGitignoreHookEntries = [
|
|
|
9
9
|
"!hooks/transforms/**",
|
|
10
10
|
];
|
|
11
11
|
|
|
12
|
+
const kOpenclawGitignoreCronRuntimeEntries = [
|
|
13
|
+
"# OpenClaw cron runtime state (local only; job definitions stay in cron/jobs.json)",
|
|
14
|
+
"cron/jobs-state.json",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const kOpenclawGitignoreAppendEntries = [
|
|
18
|
+
...kOpenclawGitignoreHookEntries,
|
|
19
|
+
...kOpenclawGitignoreCronRuntimeEntries,
|
|
20
|
+
];
|
|
21
|
+
|
|
12
22
|
const buildManagedPaths = ({ openclawDir, pathModule = path }) => {
|
|
13
23
|
const internalDir = pathModule.join(openclawDir, kInternalDirName);
|
|
14
24
|
return {
|
|
@@ -89,7 +99,7 @@ const migrateManagedInternalFiles = ({
|
|
|
89
99
|
const raw = String(fs.readFileSync(gitignorePath, "utf8") || "");
|
|
90
100
|
const existingLines = raw.split(/\r?\n/);
|
|
91
101
|
const existingSet = new Set(existingLines.map((line) => line.trim()));
|
|
92
|
-
const missing =
|
|
102
|
+
const missing = kOpenclawGitignoreAppendEntries.filter(
|
|
93
103
|
(line) => !existingSet.has(line),
|
|
94
104
|
);
|
|
95
105
|
if (missing.length) {
|