@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.
Files changed (43) hide show
  1. package/lib/public/dist/app.bundle.js +1773 -1747
  2. package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +4 -5
  3. package/lib/public/js/components/agents-tab/agent-pairing-section.js +11 -5
  4. package/lib/public/js/components/cron-tab/cron-helpers.js +13 -1
  5. package/lib/public/js/components/cron-tab/use-cron-tab.js +4 -3
  6. package/lib/public/js/components/general/index.js +6 -1
  7. package/lib/public/js/components/general/use-general-tab.js +17 -20
  8. package/lib/public/js/components/models-tab/index.js +5 -1
  9. package/lib/public/js/components/models-tab/model-picker.js +52 -0
  10. package/lib/public/js/components/onboarding/use-welcome-pairing.js +4 -1
  11. package/lib/public/js/components/pairings.js +75 -4
  12. package/lib/public/js/components/welcome/use-welcome.js +37 -8
  13. package/lib/public/js/hooks/usePolling.js +46 -13
  14. package/lib/public/js/lib/model-config.js +6 -2
  15. package/lib/server/agents/channels.js +53 -9
  16. package/lib/server/commands.js +4 -1
  17. package/lib/server/constants.js +14 -3
  18. package/lib/server/cost-utils.js +9 -0
  19. package/lib/server/cron-service.js +12 -1
  20. package/lib/server/db/doctor/index.js +9 -0
  21. package/lib/server/db/usage/index.js +13 -0
  22. package/lib/server/db/watchdog/index.js +13 -1
  23. package/lib/server/db/webhooks/index.js +13 -1
  24. package/lib/server/gateway.js +119 -8
  25. package/lib/server/init/register-server-routes.js +3 -0
  26. package/lib/server/init/runtime-init.js +2 -0
  27. package/lib/server/internal-files-migration.js +11 -1
  28. package/lib/server/model-catalog-bootstrap.json +3193 -0
  29. package/lib/server/model-catalog-cache.js +124 -32
  30. package/lib/server/onboarding/github.js +79 -2
  31. package/lib/server/onboarding/index.js +2 -9
  32. package/lib/server/onboarding/openclaw.js +18 -4
  33. package/lib/server/openclaw-runtime-env.js +55 -0
  34. package/lib/server/openclaw-version.js +2 -1
  35. package/lib/server/routes/models.js +28 -0
  36. package/lib/server/routes/pairings.js +106 -15
  37. package/lib/server/usage-tracker-config.js +28 -3
  38. package/lib/server/utils/command-output.js +11 -0
  39. package/lib/server.js +4 -0
  40. package/lib/setup/gitignore +2 -0
  41. package/package.json +2 -2
  42. package/patches/openclaw+2026.4.23.patch +63 -0
  43. 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.4",
26
- preferredKeys: ["openai-codex/gpt-5.4"],
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 clawCmd(addArgs.join(" "), {
270
- quiet: true,
271
- timeoutMs: 30000,
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 clawCmd(
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 clawCmd(addArgs.join(" "), {
889
- quiet: true,
890
- timeoutMs: 30000,
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 ||
@@ -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
  }
@@ -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 kFallbackOnboardingModels = [
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.4",
116
+ key: "openai-codex/gpt-5.5",
111
117
  provider: "openai-codex",
112
- label: "GPT-5.4",
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;
@@ -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 command = `cron edit ${shellEscapeArg(safeJobId)} --message ${shellEscapeArg(message || "")}`;
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,
@@ -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
- ...process.env,
46
- HOME: kRootDir,
47
- OPENCLAW_HOME: kRootDir,
48
- OPENCLAW_CONFIG_PATH: `${OPENCLAW_DIR}/openclaw.json`,
49
- OPENCLAW_STATE_DIR: OPENCLAW_DIR,
50
- XDG_CONFIG_HOME: OPENCLAW_DIR,
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 = kOpenclawGitignoreHookEntries.filter(
102
+ const missing = kOpenclawGitignoreAppendEntries.filter(
93
103
  (line) => !existingSet.has(line),
94
104
  );
95
105
  if (missing.length) {