@chrysb/alphaclaw 0.9.18 → 0.9.20
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 +3 -0
- package/bin/alphaclaw.js +43 -7
- package/lib/cli/git-runtime.js +41 -0
- package/lib/cli/openclaw-config-restore.js +1 -1
- package/lib/scripts/git +1 -1
- package/lib/scripts/migrate-openclaw-codex.js +17 -0
- package/lib/server/agents/shared.js +18 -2
- package/lib/server/auth-profiles.js +15 -4
- package/lib/server/constants.js +1 -1
- package/lib/server/env.js +71 -20
- package/lib/server/gateway.js +12 -87
- package/lib/server/onboarding/cron.js +8 -0
- package/lib/server/openclaw-codex-migration.js +78 -0
- package/lib/server/openclaw-config-migrations.js +77 -0
- package/lib/server/openclaw-thinking.js +26 -1
- package/lib/server/routes/system.js +4 -0
- package/lib/server/usage-tracker-config.js +5 -1
- package/lib/setup/hourly-git-sync.sh +17 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -157,6 +157,9 @@ The built-in watchdog monitors gateway health and recovers from failures automat
|
|
|
157
157
|
| `WATCHDOG_NOTIFICATIONS_DISABLED` | Optional | Disable watchdog notifications (`true`/`false`) |
|
|
158
158
|
| `PORT` | Optional | Server port (default `3000`) |
|
|
159
159
|
| `ALPHACLAW_ROOT_DIR` | Optional | Data directory (default `/data`) |
|
|
160
|
+
| `ALPHACLAW_SKIP_SYSTEM_CRON_INSTALL` | Optional | Skip writes to `/etc/cron.d` while keeping cron config (`true`/`false`); the managed hourly script still exits when sync is disabled |
|
|
161
|
+
| `ALPHACLAW_GIT_SHIM_PATH` | Optional | Install the managed git auth shim at this path and prepend its directory to runtime `PATH` (default `/usr/local/bin/git`) |
|
|
162
|
+
| `ALPHACLAW_GIT_ASKPASS_PATH` | Optional | Install the git askpass helper at this path (default `$TMPDIR/alphaclaw-git-askpass.sh`) |
|
|
160
163
|
| `TRUST_PROXY_HOPS` | Optional | Trust proxy hop count for correct client IP |
|
|
161
164
|
| `REMOTE_MCP_URL` | Optional | Upstream remote MCP server URL. When set together with `REMOTE_MCP_API_TOKEN`, AlphaClaw writes a managed `mcp.servers.<name>` entry to `openclaw.json` on every gateway start. |
|
|
162
165
|
| `REMOTE_MCP_API_TOKEN` | Optional | Bearer token for the remote MCP server. Persisted in `openclaw.json` as the `${REMOTE_MCP_API_TOKEN}` reference, never as plaintext. |
|
package/bin/alphaclaw.js
CHANGED
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const os = require("os");
|
|
6
6
|
const path = require("path");
|
|
7
|
-
const { execSync } = require("child_process");
|
|
7
|
+
const { execFileSync, execSync } = require("child_process");
|
|
8
8
|
const {
|
|
9
|
+
shouldSkipSystemCronInstall,
|
|
10
|
+
resolveGitAskPassPath,
|
|
11
|
+
resolveGitShimPath,
|
|
12
|
+
prependGitShimDirToPath,
|
|
9
13
|
normalizeGitSyncFilePath,
|
|
10
14
|
validateGitSyncFilePath,
|
|
11
15
|
resolveRealGitPath,
|
|
@@ -16,6 +20,9 @@ const {
|
|
|
16
20
|
restoreMissingOpenclawConfigFromRemote,
|
|
17
21
|
} = require("../lib/cli/openclaw-config-restore");
|
|
18
22
|
const { buildSecretReplacements } = require("../lib/server/helpers");
|
|
23
|
+
const {
|
|
24
|
+
migrateLegacyTelegramStreamingConfig,
|
|
25
|
+
} = require("../lib/server/openclaw-config-migrations");
|
|
19
26
|
const {
|
|
20
27
|
migrateManagedInternalFiles,
|
|
21
28
|
} = require("../lib/server/internal-files-migration");
|
|
@@ -278,7 +285,7 @@ const runGitSync = () => {
|
|
|
278
285
|
}
|
|
279
286
|
|
|
280
287
|
const realGitPath = resolveRealGitPath({
|
|
281
|
-
shimPath:
|
|
288
|
+
shimPath: resolveGitShimPath(),
|
|
282
289
|
});
|
|
283
290
|
if (!realGitPath) {
|
|
284
291
|
console.error(
|
|
@@ -631,7 +638,11 @@ if (fs.existsSync(hourlyGitSyncPath)) {
|
|
|
631
638
|
}
|
|
632
639
|
|
|
633
640
|
const cronFilePath = "/etc/cron.d/openclaw-hourly-sync";
|
|
634
|
-
if (
|
|
641
|
+
if (shouldSkipSystemCronInstall()) {
|
|
642
|
+
console.log(
|
|
643
|
+
"[alphaclaw] System cron setup skipped by ALPHACLAW_SKIP_SYSTEM_CRON_INSTALL",
|
|
644
|
+
);
|
|
645
|
+
} else if (cronEnabled) {
|
|
635
646
|
const cronContent = [
|
|
636
647
|
"SHELL=/bin/bash",
|
|
637
648
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
@@ -722,6 +733,20 @@ if (fs.existsSync(path.join(openclawDir, ".git"))) {
|
|
|
722
733
|
}
|
|
723
734
|
}
|
|
724
735
|
|
|
736
|
+
if (fs.existsSync(configPath)) {
|
|
737
|
+
try {
|
|
738
|
+
execFileSync(process.execPath, [
|
|
739
|
+
path.join(__dirname, "..", "lib", "scripts", "migrate-openclaw-codex.js"),
|
|
740
|
+
], {
|
|
741
|
+
env: process.env,
|
|
742
|
+
stdio: "inherit",
|
|
743
|
+
timeout: 60_000,
|
|
744
|
+
});
|
|
745
|
+
} catch (error) {
|
|
746
|
+
console.error(`[alphaclaw] Codex migration process failed: ${error.message}`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
725
750
|
if (fs.existsSync(configPath)) {
|
|
726
751
|
console.log("[alphaclaw] Config exists, reconciling channels...");
|
|
727
752
|
|
|
@@ -732,7 +757,10 @@ if (fs.existsSync(configPath)) {
|
|
|
732
757
|
if (!cfg.plugins.load) cfg.plugins.load = {};
|
|
733
758
|
if (!Array.isArray(cfg.plugins.load.paths)) cfg.plugins.load.paths = [];
|
|
734
759
|
if (!cfg.plugins.entries) cfg.plugins.entries = {};
|
|
735
|
-
let changed =
|
|
760
|
+
let changed = migrateLegacyTelegramStreamingConfig(cfg);
|
|
761
|
+
if (changed) {
|
|
762
|
+
console.log("[alphaclaw] Migrated legacy Telegram streaming config");
|
|
763
|
+
}
|
|
736
764
|
|
|
737
765
|
if (process.env.TELEGRAM_BOT_TOKEN && !cfg.channels.telegram) {
|
|
738
766
|
cfg.channels.telegram = {
|
|
@@ -819,11 +847,17 @@ try {
|
|
|
819
847
|
|
|
820
848
|
try {
|
|
821
849
|
const gitAskPassSrc = path.join(__dirname, "..", "lib", "scripts", "git-askpass");
|
|
822
|
-
const gitAskPassDest =
|
|
850
|
+
const gitAskPassDest = resolveGitAskPassPath({
|
|
851
|
+
tmpDir: os.tmpdir(),
|
|
852
|
+
});
|
|
823
853
|
const gitShimTemplatePath = path.join(__dirname, "..", "lib", "scripts", "git");
|
|
824
|
-
const gitShimDest =
|
|
854
|
+
const gitShimDest = resolveGitShimPath();
|
|
855
|
+
process.env.PATH = prependGitShimDirToPath({
|
|
856
|
+
shimPath: gitShimDest,
|
|
857
|
+
});
|
|
825
858
|
|
|
826
859
|
if (fs.existsSync(gitAskPassSrc)) {
|
|
860
|
+
fs.mkdirSync(path.dirname(gitAskPassDest), { recursive: true });
|
|
827
861
|
fs.copyFileSync(gitAskPassSrc, gitAskPassDest);
|
|
828
862
|
fs.chmodSync(gitAskPassDest, 0o755);
|
|
829
863
|
}
|
|
@@ -837,7 +871,9 @@ try {
|
|
|
837
871
|
const gitShimTemplate = fs.readFileSync(gitShimTemplatePath, "utf8");
|
|
838
872
|
const gitShimContent = gitShimTemplate
|
|
839
873
|
.replace("@@REAL_GIT@@", realGitPath)
|
|
840
|
-
.replace("@@OPENCLAW_REPO_ROOT@@", openclawDir)
|
|
874
|
+
.replace("@@OPENCLAW_REPO_ROOT@@", openclawDir)
|
|
875
|
+
.replace("@@ASKPASS_PATH@@", gitAskPassDest);
|
|
876
|
+
fs.mkdirSync(path.dirname(gitShimDest), { recursive: true });
|
|
841
877
|
fs.writeFileSync(gitShimDest, gitShimContent, { mode: 0o755 });
|
|
842
878
|
console.log("[alphaclaw] git auth shim installed");
|
|
843
879
|
}
|
package/lib/cli/git-runtime.js
CHANGED
|
@@ -2,6 +2,43 @@ const fs = require("fs");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { execSync } = require("child_process");
|
|
4
4
|
|
|
5
|
+
const isEnabledEnvFlag = (value) =>
|
|
6
|
+
["1", "true", "yes", "on"].includes(
|
|
7
|
+
String(value || "").trim().toLowerCase(),
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
const shouldSkipSystemCronInstall = ({ env = process.env } = {}) =>
|
|
11
|
+
isEnabledEnvFlag(env.ALPHACLAW_SKIP_SYSTEM_CRON_INSTALL);
|
|
12
|
+
|
|
13
|
+
const resolveGitAskPassPath = ({ env = process.env, tmpDir = "/tmp" } = {}) => {
|
|
14
|
+
const explicitPath = String(env.ALPHACLAW_GIT_ASKPASS_PATH || "").trim();
|
|
15
|
+
if (explicitPath) return explicitPath;
|
|
16
|
+
|
|
17
|
+
const runtimeTmpDir =
|
|
18
|
+
String(env.TMPDIR || "").trim() || String(tmpDir || "").trim() || "/tmp";
|
|
19
|
+
return path.join(runtimeTmpDir, "alphaclaw-git-askpass.sh");
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const resolveGitShimPath = ({ env = process.env } = {}) => {
|
|
23
|
+
const explicitPath = String(env.ALPHACLAW_GIT_SHIM_PATH || "").trim();
|
|
24
|
+
return explicitPath || "/usr/local/bin/git";
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const prependGitShimDirToPath = ({
|
|
28
|
+
env = process.env,
|
|
29
|
+
shimPath = resolveGitShimPath({ env }),
|
|
30
|
+
} = {}) => {
|
|
31
|
+
if (!String(env.ALPHACLAW_GIT_SHIM_PATH || "").trim()) {
|
|
32
|
+
return env.PATH || process.env.PATH || "";
|
|
33
|
+
}
|
|
34
|
+
const shimDir = path.dirname(String(shimPath || "").trim());
|
|
35
|
+
if (!shimDir || shimDir === ".") return env.PATH || process.env.PATH || "";
|
|
36
|
+
const pathEntries = String(env.PATH || process.env.PATH || "")
|
|
37
|
+
.split(path.delimiter)
|
|
38
|
+
.filter((entry) => entry && entry !== shimDir);
|
|
39
|
+
return [shimDir, ...pathEntries].join(path.delimiter);
|
|
40
|
+
};
|
|
41
|
+
|
|
5
42
|
const normalizeGitSyncFilePath = (requestedFilePath) => {
|
|
6
43
|
const rawPath = String(requestedFilePath || "").trim();
|
|
7
44
|
if (!rawPath) return "";
|
|
@@ -90,6 +127,10 @@ const shouldRefreshHourlyGitSyncScript = ({
|
|
|
90
127
|
};
|
|
91
128
|
|
|
92
129
|
module.exports = {
|
|
130
|
+
shouldSkipSystemCronInstall,
|
|
131
|
+
resolveGitAskPassPath,
|
|
132
|
+
resolveGitShimPath,
|
|
133
|
+
prependGitShimDirToPath,
|
|
93
134
|
normalizeGitSyncFilePath,
|
|
94
135
|
validateGitSyncFilePath,
|
|
95
136
|
resolveRealGitPath,
|
|
@@ -27,7 +27,7 @@ const resolveCurrentBranch = ({ execSyncImpl, openclawDir }) => {
|
|
|
27
27
|
|
|
28
28
|
const createGitEnv = ({ fsModule, osModule, env, processId }) => {
|
|
29
29
|
const githubToken = String(env.GITHUB_TOKEN || "").trim();
|
|
30
|
-
const gitEnv = { ...env };
|
|
30
|
+
const gitEnv = { ...env, PATH: env.PATH || process.env.PATH };
|
|
31
31
|
if (!githubToken) {
|
|
32
32
|
return { gitEnv, askPassPath: "" };
|
|
33
33
|
}
|
package/lib/scripts/git
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { migrateLegacyCodexState } = require("../server/openclaw-codex-migration");
|
|
4
|
+
|
|
5
|
+
migrateLegacyCodexState()
|
|
6
|
+
.then((result) => {
|
|
7
|
+
if (result.changed) {
|
|
8
|
+
console.log("[alphaclaw] Migrated legacy OpenAI Codex model and auth state");
|
|
9
|
+
}
|
|
10
|
+
for (const warning of result.warnings) {
|
|
11
|
+
console.warn(`[alphaclaw] Codex migration warning: ${warning}`);
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
.catch((error) => {
|
|
15
|
+
console.error(`[alphaclaw] Codex migration failed: ${error.message}`);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
});
|
|
@@ -505,12 +505,16 @@ const readPairedCountsByAccount = ({
|
|
|
505
505
|
} catch {}
|
|
506
506
|
|
|
507
507
|
for (const accountId of counts.keys()) {
|
|
508
|
-
if (String(channelId || "").trim() === "whatsapp") continue;
|
|
509
508
|
const accountConfig =
|
|
510
509
|
accountId === "default" &&
|
|
511
510
|
!(config.accounts && typeof config.accounts === "object")
|
|
512
511
|
? config
|
|
513
512
|
: config.accounts?.[accountId] || {};
|
|
513
|
+
if (String(channelId || "").trim() === "whatsapp") {
|
|
514
|
+
if (!hasSavedWhatsAppCredentials({ fsImpl, OPENCLAW_DIR, accountId })) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
514
518
|
const inlineAllowFrom = accountConfig?.allowFrom;
|
|
515
519
|
if (!Array.isArray(inlineAllowFrom)) continue;
|
|
516
520
|
counts.set(
|
|
@@ -616,7 +620,15 @@ const listConfiguredChannelAccounts = ({ fsImpl, OPENCLAW_DIR, cfg }) => {
|
|
|
616
620
|
status:
|
|
617
621
|
Number(pairedCounts.get(accountId) || 0) > 0
|
|
618
622
|
? "paired"
|
|
619
|
-
:
|
|
623
|
+
: hasImplicitWhatsAppSelfPairing({
|
|
624
|
+
fsImpl,
|
|
625
|
+
OPENCLAW_DIR,
|
|
626
|
+
channelId,
|
|
627
|
+
accountId,
|
|
628
|
+
accountConfig,
|
|
629
|
+
})
|
|
630
|
+
? "paired"
|
|
631
|
+
: "configured",
|
|
620
632
|
};
|
|
621
633
|
}),
|
|
622
634
|
};
|
|
@@ -764,6 +776,10 @@ module.exports = {
|
|
|
764
776
|
resolveCredentialsDirPath,
|
|
765
777
|
resolveWhatsAppCredentialCandidatePaths,
|
|
766
778
|
hasSavedWhatsAppCredentials,
|
|
779
|
+
normalizeChannelAccountId,
|
|
780
|
+
resolveCredentialPairingAccountId,
|
|
781
|
+
hasImplicitWhatsAppSelfPairing,
|
|
782
|
+
readPairedCountsByAccount,
|
|
767
783
|
resolveAgentWorkspacePath,
|
|
768
784
|
loadConfig,
|
|
769
785
|
saveConfig,
|
|
@@ -281,7 +281,12 @@ const createAuthProfiles = () => {
|
|
|
281
281
|
|
|
282
282
|
// ── Legacy Codex-specific wrappers ──
|
|
283
283
|
|
|
284
|
-
const listCodexProfiles = () =>
|
|
284
|
+
const listCodexProfiles = () =>
|
|
285
|
+
listProfiles().filter(
|
|
286
|
+
(profile) =>
|
|
287
|
+
profile.type === "oauth" &&
|
|
288
|
+
(profile.provider === "openai" || profile.provider === "openai-codex"),
|
|
289
|
+
);
|
|
285
290
|
|
|
286
291
|
const getCodexProfile = () => {
|
|
287
292
|
const profiles = listCodexProfiles();
|
|
@@ -299,7 +304,7 @@ const createAuthProfiles = () => {
|
|
|
299
304
|
const upsertCodexProfile = ({ access, refresh, expires, accountId }) => {
|
|
300
305
|
upsertProfile(CODEX_PROFILE_ID, {
|
|
301
306
|
type: "oauth",
|
|
302
|
-
provider: "openai
|
|
307
|
+
provider: "openai",
|
|
303
308
|
access,
|
|
304
309
|
refresh,
|
|
305
310
|
expires,
|
|
@@ -311,7 +316,10 @@ const createAuthProfiles = () => {
|
|
|
311
316
|
const store = loadAuthStore();
|
|
312
317
|
let changed = false;
|
|
313
318
|
for (const [id, cred] of Object.entries(store.profiles || {})) {
|
|
314
|
-
if (
|
|
319
|
+
if (
|
|
320
|
+
cred?.type === "oauth" &&
|
|
321
|
+
(cred.provider === "openai" || cred.provider === "openai-codex")
|
|
322
|
+
) {
|
|
315
323
|
delete store.profiles[id];
|
|
316
324
|
changed = true;
|
|
317
325
|
}
|
|
@@ -321,7 +329,10 @@ const createAuthProfiles = () => {
|
|
|
321
329
|
if (!canSyncOpenclawAuthReferences()) return changed;
|
|
322
330
|
let cfg = loadOpenclawConfig();
|
|
323
331
|
for (const [id, cred] of Object.entries(cfg.auth?.profiles || {})) {
|
|
324
|
-
if (
|
|
332
|
+
if (
|
|
333
|
+
cred?.mode === "oauth" &&
|
|
334
|
+
(cred.provider === "openai" || cred.provider === "openai-codex")
|
|
335
|
+
) {
|
|
325
336
|
cfg = removeConfigAuthReference(cfg, id);
|
|
326
337
|
}
|
|
327
338
|
}
|
package/lib/server/constants.js
CHANGED
|
@@ -28,7 +28,7 @@ const AUTH_PROFILES_PATH = path.join(
|
|
|
28
28
|
"agent",
|
|
29
29
|
"auth-profiles.json",
|
|
30
30
|
);
|
|
31
|
-
const CODEX_PROFILE_ID = "openai
|
|
31
|
+
const CODEX_PROFILE_ID = "openai:codex-cli";
|
|
32
32
|
const CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
33
33
|
const CODEX_OAUTH_AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
34
34
|
const CODEX_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
package/lib/server/env.js
CHANGED
|
@@ -1,21 +1,48 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const { ENV_FILE_PATH, kKnownVars } = require("./constants");
|
|
3
3
|
|
|
4
|
+
const kSensitiveEnvKeyPattern = /token|key|password/i;
|
|
5
|
+
const kEnvWatchDebounceMs = 250;
|
|
6
|
+
let envWatchDebounceTimer = null;
|
|
7
|
+
let lastLoadedEnvSignature = null;
|
|
8
|
+
let pendingSelfWriteSignature = null;
|
|
9
|
+
|
|
10
|
+
const normalizeEnvVars = (vars) => {
|
|
11
|
+
const byKey = new Map();
|
|
12
|
+
for (const entry of vars || []) {
|
|
13
|
+
const key = String(entry?.key || "").trim();
|
|
14
|
+
if (!key) continue;
|
|
15
|
+
if (byKey.has(key)) byKey.delete(key);
|
|
16
|
+
byKey.set(key, {
|
|
17
|
+
key,
|
|
18
|
+
value: String(entry?.value || ""),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return Array.from(byKey.values());
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const buildEnvSignature = (vars) =>
|
|
25
|
+
JSON.stringify(normalizeEnvVars(vars).map(({ key, value }) => [key, value]));
|
|
26
|
+
|
|
27
|
+
const readRawEnvFile = () => {
|
|
28
|
+
const content = fs.readFileSync(ENV_FILE_PATH, "utf8");
|
|
29
|
+
const vars = [];
|
|
30
|
+
for (const line of content.split("\n")) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
33
|
+
const eqIdx = trimmed.indexOf("=");
|
|
34
|
+
if (eqIdx === -1) continue;
|
|
35
|
+
vars.push({
|
|
36
|
+
key: trimmed.slice(0, eqIdx).trim(),
|
|
37
|
+
value: trimmed.slice(eqIdx + 1),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return vars;
|
|
41
|
+
};
|
|
42
|
+
|
|
4
43
|
const readEnvFile = () => {
|
|
5
44
|
try {
|
|
6
|
-
|
|
7
|
-
const vars = [];
|
|
8
|
-
for (const line of content.split("\n")) {
|
|
9
|
-
const trimmed = line.trim();
|
|
10
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
11
|
-
const eqIdx = trimmed.indexOf("=");
|
|
12
|
-
if (eqIdx === -1) continue;
|
|
13
|
-
vars.push({
|
|
14
|
-
key: trimmed.slice(0, eqIdx),
|
|
15
|
-
value: trimmed.slice(eqIdx + 1),
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
return vars;
|
|
45
|
+
return normalizeEnvVars(readRawEnvFile());
|
|
19
46
|
} catch {
|
|
20
47
|
return [];
|
|
21
48
|
}
|
|
@@ -23,22 +50,24 @@ const readEnvFile = () => {
|
|
|
23
50
|
|
|
24
51
|
const writeEnvFile = (vars) => {
|
|
25
52
|
const lines = [];
|
|
26
|
-
|
|
27
|
-
|
|
53
|
+
const normalizedVars = normalizeEnvVars(vars);
|
|
54
|
+
for (const { key, value } of normalizedVars) {
|
|
28
55
|
lines.push(`${key}=${String(value || "")}`);
|
|
29
56
|
}
|
|
30
57
|
fs.writeFileSync(ENV_FILE_PATH, lines.join("\n"));
|
|
58
|
+
pendingSelfWriteSignature = buildEnvSignature(normalizedVars);
|
|
31
59
|
};
|
|
32
60
|
|
|
33
61
|
const reloadEnv = () => {
|
|
34
62
|
const vars = readEnvFile();
|
|
63
|
+
const signature = buildEnvSignature(vars);
|
|
35
64
|
const fileKeys = new Set(vars.map((v) => v.key));
|
|
36
65
|
let changed = false;
|
|
37
66
|
|
|
38
67
|
for (const { key, value } of vars) {
|
|
39
68
|
if (value && value !== process.env[key]) {
|
|
40
69
|
console.log(
|
|
41
|
-
`[alphaclaw] Env updated: ${key}=${
|
|
70
|
+
`[alphaclaw] Env updated: ${key}=${kSensitiveEnvKeyPattern.test(key) ? "***" : value}`,
|
|
42
71
|
);
|
|
43
72
|
process.env[key] = value;
|
|
44
73
|
changed = true;
|
|
@@ -58,21 +87,43 @@ const reloadEnv = () => {
|
|
|
58
87
|
}
|
|
59
88
|
}
|
|
60
89
|
|
|
90
|
+
lastLoadedEnvSignature = signature;
|
|
61
91
|
return changed;
|
|
62
92
|
};
|
|
63
93
|
|
|
94
|
+
const readEnvFileSignature = () => {
|
|
95
|
+
try {
|
|
96
|
+
return buildEnvSignature(readRawEnvFile());
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
64
102
|
const startEnvWatcher = () => {
|
|
65
103
|
try {
|
|
66
104
|
fs.watchFile(ENV_FILE_PATH, { interval: 2000 }, () => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
105
|
+
if (envWatchDebounceTimer) clearTimeout(envWatchDebounceTimer);
|
|
106
|
+
envWatchDebounceTimer = setTimeout(() => {
|
|
107
|
+
envWatchDebounceTimer = null;
|
|
108
|
+
const signature = readEnvFileSignature();
|
|
109
|
+
if (signature && signature === pendingSelfWriteSignature) {
|
|
110
|
+
pendingSelfWriteSignature = null;
|
|
111
|
+
lastLoadedEnvSignature = signature;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
pendingSelfWriteSignature = null;
|
|
115
|
+
if (signature && signature === lastLoadedEnvSignature) return;
|
|
116
|
+
console.log(
|
|
117
|
+
`[alphaclaw] ${ENV_FILE_PATH} changed externally, reloading...`,
|
|
118
|
+
);
|
|
119
|
+
reloadEnv();
|
|
120
|
+
}, kEnvWatchDebounceMs);
|
|
71
121
|
});
|
|
72
122
|
} catch {}
|
|
73
123
|
};
|
|
74
124
|
|
|
75
125
|
module.exports = {
|
|
126
|
+
normalizeEnvVars,
|
|
76
127
|
readEnvFile,
|
|
77
128
|
writeEnvFile,
|
|
78
129
|
reloadEnv,
|
package/lib/server/gateway.js
CHANGED
|
@@ -11,6 +11,10 @@ const {
|
|
|
11
11
|
kOnboardingMarkerPath,
|
|
12
12
|
kRootDir,
|
|
13
13
|
} = require("./constants");
|
|
14
|
+
const {
|
|
15
|
+
normalizeChannelAccountId,
|
|
16
|
+
readPairedCountsByAccount,
|
|
17
|
+
} = require("./agents/shared");
|
|
14
18
|
const { withOpenclawStartupEnv } = require("./openclaw-runtime-env");
|
|
15
19
|
const { isOpenAiCompatApiEnabled } = require("./alphaclaw-config");
|
|
16
20
|
|
|
@@ -203,18 +207,6 @@ const getGatewayPort = () => {
|
|
|
203
207
|
|
|
204
208
|
const getGatewayUrl = () => `http://${GATEWAY_HOST}:${getGatewayPort()}`;
|
|
205
209
|
|
|
206
|
-
const normalizeChannelAccountId = (value) => String(value || "").trim() || "default";
|
|
207
|
-
|
|
208
|
-
const resolveCredentialPairingAccountId = ({ channel, fileName }) => {
|
|
209
|
-
const prefix = `${String(channel || "").trim()}-`;
|
|
210
|
-
const suffix = "-allowFrom.json";
|
|
211
|
-
if (!String(fileName || "").startsWith(prefix) || !String(fileName || "").endsWith(suffix)) {
|
|
212
|
-
return "";
|
|
213
|
-
}
|
|
214
|
-
const rawAccountId = String(fileName || "").slice(prefix.length, -suffix.length);
|
|
215
|
-
return normalizeChannelAccountId(rawAccountId);
|
|
216
|
-
};
|
|
217
|
-
|
|
218
210
|
const isGatewayRunning = () =>
|
|
219
211
|
new Promise((resolve) => {
|
|
220
212
|
const sock = net.createConnection(getGatewayPort(), GATEWAY_HOST);
|
|
@@ -781,32 +773,6 @@ const getChannelStatus = () => {
|
|
|
781
773
|
);
|
|
782
774
|
const credDir = `${OPENCLAW_DIR}/credentials`;
|
|
783
775
|
const channels = {};
|
|
784
|
-
const hasImplicitWhatsAppSelfPairing = ({ accountId, accountConfig }) => {
|
|
785
|
-
if (!accountConfig || typeof accountConfig !== "object") return false;
|
|
786
|
-
if (accountConfig.selfChatMode === false) return false;
|
|
787
|
-
if (String(accountConfig.dmPolicy || "").trim().toLowerCase() === "disabled") {
|
|
788
|
-
return false;
|
|
789
|
-
}
|
|
790
|
-
const candidatePaths = [
|
|
791
|
-
`${credDir}/whatsapp/${accountId}/creds.json`,
|
|
792
|
-
...(accountId === "default" ? [`${credDir}/creds.json`] : []),
|
|
793
|
-
];
|
|
794
|
-
const matches = candidatePaths.map((targetPath) => {
|
|
795
|
-
try {
|
|
796
|
-
return {
|
|
797
|
-
path: targetPath,
|
|
798
|
-
exists: !!String(fs.readFileSync(targetPath, "utf8") || "").trim(),
|
|
799
|
-
};
|
|
800
|
-
} catch (error) {
|
|
801
|
-
return {
|
|
802
|
-
path: targetPath,
|
|
803
|
-
exists: false,
|
|
804
|
-
error: String(error?.message || error || "read failed"),
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
});
|
|
808
|
-
return matches.some((entry) => entry.exists);
|
|
809
|
-
};
|
|
810
776
|
|
|
811
777
|
for (const ch of Object.keys(kChannelDefs)) {
|
|
812
778
|
const channelConfig =
|
|
@@ -836,55 +802,14 @@ const getChannelStatus = () => {
|
|
|
836
802
|
});
|
|
837
803
|
if (!hasConfiguredToken) continue;
|
|
838
804
|
|
|
839
|
-
const pairedByAccount =
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
(f) => f.startsWith(`${ch}-`) && f.endsWith("-allowFrom.json"),
|
|
848
|
-
);
|
|
849
|
-
for (const file of files) {
|
|
850
|
-
const accountId = resolveCredentialPairingAccountId({
|
|
851
|
-
channel: ch,
|
|
852
|
-
fileName: file,
|
|
853
|
-
});
|
|
854
|
-
if (!accountId || !configuredAccountIds.has(accountId)) continue;
|
|
855
|
-
const data = JSON.parse(
|
|
856
|
-
fs.readFileSync(`${credDir}/${file}`, "utf8"),
|
|
857
|
-
);
|
|
858
|
-
const nextCount =
|
|
859
|
-
Number(pairedByAccount.get(accountId) || 0)
|
|
860
|
-
+ (Array.isArray(data.allowFrom) ? data.allowFrom.length : 0);
|
|
861
|
-
pairedByAccount.set(accountId, nextCount);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
} catch {}
|
|
865
|
-
for (const [accountId, accountConfig] of accountEntries) {
|
|
866
|
-
if (ch === "whatsapp") continue;
|
|
867
|
-
const inlineAllowFrom = accountConfig?.allowFrom;
|
|
868
|
-
if (!Array.isArray(inlineAllowFrom)) continue;
|
|
869
|
-
const normalizedAccountId = normalizeChannelAccountId(accountId);
|
|
870
|
-
const nextCount =
|
|
871
|
-
Number(pairedByAccount.get(normalizedAccountId) || 0) + inlineAllowFrom.length;
|
|
872
|
-
pairedByAccount.set(normalizedAccountId, nextCount);
|
|
873
|
-
}
|
|
874
|
-
if (ch === "whatsapp") {
|
|
875
|
-
for (const [accountId, accountConfig] of accountEntries) {
|
|
876
|
-
const normalizedAccountId = normalizeChannelAccountId(accountId);
|
|
877
|
-
if (Number(pairedByAccount.get(normalizedAccountId) || 0) > 0) continue;
|
|
878
|
-
if (
|
|
879
|
-
hasImplicitWhatsAppSelfPairing({
|
|
880
|
-
accountId: normalizedAccountId,
|
|
881
|
-
accountConfig,
|
|
882
|
-
})
|
|
883
|
-
) {
|
|
884
|
-
pairedByAccount.set(normalizedAccountId, 1);
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
805
|
+
const pairedByAccount = readPairedCountsByAccount({
|
|
806
|
+
fsImpl: fs,
|
|
807
|
+
OPENCLAW_DIR,
|
|
808
|
+
channelId: ch,
|
|
809
|
+
accountIds: Array.from(configuredAccountIds),
|
|
810
|
+
config: channelConfig,
|
|
811
|
+
});
|
|
812
|
+
|
|
888
813
|
const accounts = Object.fromEntries(
|
|
889
814
|
Array.from(pairedByAccount.entries()).map(([accountId, paired]) => [
|
|
890
815
|
accountId,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const { kSetupDir } = require("../constants");
|
|
3
3
|
const { buildManagedPaths } = require("../internal-files-migration");
|
|
4
|
+
const { shouldSkipSystemCronInstall } = require("../../cli/git-runtime");
|
|
4
5
|
|
|
5
6
|
const kHourlyGitSyncTemplatePath = path.join(kSetupDir, "hourly-git-sync.sh");
|
|
6
7
|
const kSystemCronPath = "/etc/cron.d/openclaw-hourly-sync";
|
|
@@ -37,6 +38,13 @@ const installHourlyGitSyncCron = async ({ fs, openclawDir }) => {
|
|
|
37
38
|
fs.mkdirSync(configDir, { recursive: true });
|
|
38
39
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
39
40
|
|
|
41
|
+
if (shouldSkipSystemCronInstall()) {
|
|
42
|
+
console.log(
|
|
43
|
+
"[onboard] System cron install skipped by ALPHACLAW_SKIP_SYSTEM_CRON_INSTALL",
|
|
44
|
+
);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
const cronContent = buildSystemCronFile({
|
|
41
49
|
schedule: config.schedule,
|
|
42
50
|
scriptPath: hourlyGitSyncPath,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { pathToFileURL } = require("url");
|
|
4
|
+
|
|
5
|
+
const findOpenclawDistModule = (prefix) => {
|
|
6
|
+
const entryPath = require.resolve("openclaw");
|
|
7
|
+
const distDir = path.dirname(entryPath);
|
|
8
|
+
const filename = fs
|
|
9
|
+
.readdirSync(distDir)
|
|
10
|
+
.find((name) => name.startsWith(`${prefix}-`) && name.endsWith(".js"));
|
|
11
|
+
if (!filename) throw new Error(`OpenClaw migration module not found: ${prefix}`);
|
|
12
|
+
return path.join(distDir, filename);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const writeConfig = (configPath, cfg) => {
|
|
16
|
+
fs.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\n`, "utf8");
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const migrateLegacyCodexState = async ({
|
|
20
|
+
configPath = process.env.OPENCLAW_CONFIG_PATH,
|
|
21
|
+
env = process.env,
|
|
22
|
+
} = {}) => {
|
|
23
|
+
if (!configPath || !fs.existsSync(configPath)) {
|
|
24
|
+
return { changed: false, changes: [], warnings: [] };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
28
|
+
const routeModule = await import(
|
|
29
|
+
pathToFileURL(findOpenclawDistModule("codex-route-warnings")).href
|
|
30
|
+
);
|
|
31
|
+
const authModule = await import(
|
|
32
|
+
pathToFileURL(findOpenclawDistModule("doctor-auth-flat-profiles")).href
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const changes = [];
|
|
36
|
+
const warnings = [];
|
|
37
|
+
const routeRepair = routeModule.r({ cfg, env, shouldRepair: true });
|
|
38
|
+
let nextCfg = routeRepair.cfg;
|
|
39
|
+
changes.push(...routeRepair.changes);
|
|
40
|
+
warnings.push(...routeRepair.warnings);
|
|
41
|
+
|
|
42
|
+
const profileIdMap = authModule.t({ cfg: nextCfg, env });
|
|
43
|
+
const configAuthRepair = authModule.a(nextCfg, { profileIdMap });
|
|
44
|
+
nextCfg = configAuthRepair.config;
|
|
45
|
+
changes.push(...configAuthRepair.changes);
|
|
46
|
+
warnings.push(...configAuthRepair.warnings);
|
|
47
|
+
|
|
48
|
+
if (changes.length > 0) writeConfig(configPath, nextCfg);
|
|
49
|
+
|
|
50
|
+
const storeRepair = await authModule.o({ cfg: nextCfg, env });
|
|
51
|
+
changes.push(...storeRepair.changes);
|
|
52
|
+
warnings.push(...storeRepair.warnings);
|
|
53
|
+
|
|
54
|
+
const sqliteMigration = await authModule.n({
|
|
55
|
+
cfg: nextCfg,
|
|
56
|
+
env,
|
|
57
|
+
prompter: { confirmAutoFix: async () => true },
|
|
58
|
+
});
|
|
59
|
+
changes.push(...sqliteMigration.changes);
|
|
60
|
+
warnings.push(...sqliteMigration.warnings);
|
|
61
|
+
if (sqliteMigration.configChanged) writeConfig(configPath, nextCfg);
|
|
62
|
+
|
|
63
|
+
const sessionRepair = await routeModule.i({
|
|
64
|
+
cfg: nextCfg,
|
|
65
|
+
env,
|
|
66
|
+
shouldRepair: true,
|
|
67
|
+
});
|
|
68
|
+
changes.push(...sessionRepair.changes);
|
|
69
|
+
warnings.push(...sessionRepair.warnings);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
changed: changes.length > 0,
|
|
73
|
+
changes,
|
|
74
|
+
warnings,
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
module.exports = { migrateLegacyCodexState };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const isRecord = (value) =>
|
|
2
|
+
!!value && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
|
|
4
|
+
const parseStreamingMode = (value) => {
|
|
5
|
+
if (typeof value === "boolean") return value ? "partial" : "off";
|
|
6
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
7
|
+
return ["off", "partial", "block", "progress"].includes(normalized)
|
|
8
|
+
? normalized
|
|
9
|
+
: null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const migrateLegacyTelegramStreamingEntry = (entry) => {
|
|
13
|
+
if (!isRecord(entry)) return false;
|
|
14
|
+
|
|
15
|
+
const legacyStreaming = entry.streaming;
|
|
16
|
+
const hasLegacyStreaming =
|
|
17
|
+
typeof legacyStreaming === "boolean" || typeof legacyStreaming === "string";
|
|
18
|
+
const hasLegacyFields =
|
|
19
|
+
entry.streamMode !== undefined ||
|
|
20
|
+
entry.chunkMode !== undefined ||
|
|
21
|
+
entry.blockStreaming !== undefined ||
|
|
22
|
+
entry.blockStreamingCoalesce !== undefined ||
|
|
23
|
+
entry.draftChunk !== undefined;
|
|
24
|
+
if (!hasLegacyStreaming && !hasLegacyFields) return false;
|
|
25
|
+
|
|
26
|
+
const streaming = isRecord(legacyStreaming) ? { ...legacyStreaming } : {};
|
|
27
|
+
const resolvedMode =
|
|
28
|
+
parseStreamingMode(isRecord(legacyStreaming) ? legacyStreaming.mode : legacyStreaming) ||
|
|
29
|
+
parseStreamingMode(entry.streamMode) ||
|
|
30
|
+
"partial";
|
|
31
|
+
if (streaming.mode === undefined) streaming.mode = resolvedMode;
|
|
32
|
+
|
|
33
|
+
if (entry.chunkMode !== undefined && streaming.chunkMode === undefined) {
|
|
34
|
+
streaming.chunkMode = entry.chunkMode;
|
|
35
|
+
}
|
|
36
|
+
if (entry.draftChunk !== undefined) {
|
|
37
|
+
const preview = isRecord(streaming.preview) ? { ...streaming.preview } : {};
|
|
38
|
+
if (preview.chunk === undefined) preview.chunk = entry.draftChunk;
|
|
39
|
+
streaming.preview = preview;
|
|
40
|
+
}
|
|
41
|
+
if (entry.blockStreaming !== undefined || entry.blockStreamingCoalesce !== undefined) {
|
|
42
|
+
const block = isRecord(streaming.block) ? { ...streaming.block } : {};
|
|
43
|
+
if (entry.blockStreaming !== undefined && block.enabled === undefined) {
|
|
44
|
+
block.enabled = entry.blockStreaming;
|
|
45
|
+
}
|
|
46
|
+
if (entry.blockStreamingCoalesce !== undefined && block.coalesce === undefined) {
|
|
47
|
+
block.coalesce = entry.blockStreamingCoalesce;
|
|
48
|
+
}
|
|
49
|
+
streaming.block = block;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
entry.streaming = streaming;
|
|
53
|
+
delete entry.streamMode;
|
|
54
|
+
delete entry.chunkMode;
|
|
55
|
+
delete entry.blockStreaming;
|
|
56
|
+
delete entry.blockStreamingCoalesce;
|
|
57
|
+
delete entry.draftChunk;
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const migrateLegacyTelegramStreamingConfig = (cfg = {}) => {
|
|
62
|
+
const telegram = cfg.channels?.telegram;
|
|
63
|
+
if (!isRecord(telegram)) return false;
|
|
64
|
+
|
|
65
|
+
let changed = migrateLegacyTelegramStreamingEntry(telegram);
|
|
66
|
+
if (isRecord(telegram.accounts)) {
|
|
67
|
+
for (const account of Object.values(telegram.accounts)) {
|
|
68
|
+
if (migrateLegacyTelegramStreamingEntry(account)) changed = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return changed;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
migrateLegacyTelegramStreamingConfig,
|
|
76
|
+
migrateLegacyTelegramStreamingEntry,
|
|
77
|
+
};
|
|
@@ -27,6 +27,31 @@ const loadThinkingModule = async () => {
|
|
|
27
27
|
return thinkingModulePromise;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
const isNormalizeThinkLevel = (candidate) => {
|
|
31
|
+
if (typeof candidate !== "function") return false;
|
|
32
|
+
try {
|
|
33
|
+
return (
|
|
34
|
+
candidate("off") === "off" &&
|
|
35
|
+
candidate("low") === "low" &&
|
|
36
|
+
candidate("high") === "high"
|
|
37
|
+
);
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const resolveNormalizeThinkLevel = (mod) => {
|
|
44
|
+
const candidates = [
|
|
45
|
+
mod.normalizeThinkLevel,
|
|
46
|
+
mod.f,
|
|
47
|
+
mod.p,
|
|
48
|
+
...Object.values(mod),
|
|
49
|
+
];
|
|
50
|
+
const match = candidates.find(isNormalizeThinkLevel);
|
|
51
|
+
if (!match) throw new Error("OpenClaw normalizeThinkLevel export not found");
|
|
52
|
+
return match;
|
|
53
|
+
};
|
|
54
|
+
|
|
30
55
|
const splitModelKey = (modelKey = "") => {
|
|
31
56
|
const normalized = String(modelKey || "").trim();
|
|
32
57
|
const slashIndex = normalized.indexOf("/");
|
|
@@ -55,7 +80,7 @@ const resolveThinkingApi = async () => {
|
|
|
55
80
|
return {
|
|
56
81
|
listThinkingLevelOptions: mod.listThinkingLevelOptions || mod.i,
|
|
57
82
|
resolveThinkingDefaultForModel: mod.resolveThinkingDefaultForModel || mod.s,
|
|
58
|
-
normalizeThinkLevel: mod
|
|
83
|
+
normalizeThinkLevel: resolveNormalizeThinkLevel(mod),
|
|
59
84
|
};
|
|
60
85
|
};
|
|
61
86
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { buildManagedPaths } = require("../internal-files-migration");
|
|
2
2
|
const { readOpenclawConfig } = require("../openclaw-config");
|
|
3
|
+
const { shouldSkipSystemCronInstall } = require("../../cli/git-runtime");
|
|
3
4
|
const {
|
|
4
5
|
readAlphaclawConfig,
|
|
5
6
|
updateOpenAiCompatApiFeature,
|
|
@@ -388,6 +389,9 @@ const registerSystemRoutes = ({
|
|
|
388
389
|
kSystemCronConfigPath,
|
|
389
390
|
JSON.stringify(nextConfig, null, 2),
|
|
390
391
|
);
|
|
392
|
+
if (shouldSkipSystemCronInstall()) {
|
|
393
|
+
return getSystemCronStatus();
|
|
394
|
+
}
|
|
391
395
|
if (nextConfig.enabled) {
|
|
392
396
|
fs.writeFileSync(
|
|
393
397
|
kSystemCronPath,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const { readOpenclawConfig, writeOpenclawConfig } = require("./openclaw-config");
|
|
3
|
+
const {
|
|
4
|
+
migrateLegacyTelegramStreamingConfig,
|
|
5
|
+
} = require("./openclaw-config-migrations");
|
|
3
6
|
|
|
4
7
|
const kUsageTrackerPluginPath = path.resolve(
|
|
5
8
|
__dirname,
|
|
@@ -123,7 +126,8 @@ const ensureUsageTrackerPluginConfig = ({ fsModule, openclawDir }) => {
|
|
|
123
126
|
openclawDir,
|
|
124
127
|
fallback: {},
|
|
125
128
|
});
|
|
126
|
-
const
|
|
129
|
+
const migrated = migrateLegacyTelegramStreamingConfig(cfg);
|
|
130
|
+
const changed = reconcileManagedPluginConfig(cfg) || migrated;
|
|
127
131
|
if (!changed) return false;
|
|
128
132
|
writeOpenclawConfig({
|
|
129
133
|
fsModule,
|
|
@@ -12,6 +12,23 @@ if [[ -f "$REPO/.env" ]]; then
|
|
|
12
12
|
set +a
|
|
13
13
|
fi
|
|
14
14
|
|
|
15
|
+
if [[ -f "$REPO/cron/system-sync.json" ]]; then
|
|
16
|
+
if node - "$REPO/cron/system-sync.json" <<'NODE'
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const file = process.argv[2];
|
|
19
|
+
try {
|
|
20
|
+
const config = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
21
|
+
process.exit(config && config.enabled === false ? 0 : 1);
|
|
22
|
+
} catch {
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
NODE
|
|
26
|
+
then
|
|
27
|
+
echo "hourly-git-sync: disabled by cron/system-sync.json"
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
|
|
15
32
|
# Drop cron scheduler runtime-only churn when it is metadata/timestamp-only.
|
|
16
33
|
maybe_restore_if_runtime_only() {
|
|
17
34
|
local file="$1"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrysb/alphaclaw",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.20",
|
|
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.6.11",
|
|
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.19.0"
|
|
55
55
|
}
|
|
56
56
|
}
|