@chrysb/alphaclaw 0.9.16 → 0.9.18
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 +25 -0
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +1858 -1758
- package/lib/public/js/components/agents-tab/agent-overview/model-card.js +59 -7
- package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +124 -0
- package/lib/public/js/components/api-feature-panel.js +76 -0
- package/lib/public/js/components/envars.js +1 -1
- package/lib/public/js/components/general/index.js +6 -0
- package/lib/public/js/components/general/use-general-tab.js +69 -0
- package/lib/public/js/components/row-accessory-select.js +52 -0
- package/lib/public/js/lib/api.js +26 -0
- package/lib/public/js/lib/model-catalog.js +6 -0
- package/lib/public/js/lib/model-config.js +12 -7
- package/lib/public/js/lib/storage-keys.js +4 -0
- package/lib/public/js/lib/thinking-levels.js +37 -0
- package/lib/server/agents/agents.js +33 -7
- package/lib/server/agents/channels.js +4 -2
- package/lib/server/alphaclaw-config.js +99 -0
- package/lib/server/chat-ws.js +4 -1
- package/lib/server/constants.js +73 -0
- package/lib/server/cost-utils.js +2 -0
- package/lib/server/db/auth/index.js +147 -0
- package/lib/server/db/auth/schema.js +17 -0
- package/lib/server/gateway.js +321 -20
- package/lib/server/helpers.js +1 -3
- package/lib/server/init/register-server-routes.js +45 -18
- package/lib/server/init/runtime-init.js +4 -0
- package/lib/server/init/server-lifecycle.js +1 -24
- package/lib/server/login-throttle.js +261 -60
- package/lib/server/model-catalog-bootstrap.json +5 -0
- package/lib/server/onboarding/index.js +2 -2
- package/lib/server/onboarding/openclaw.js +27 -3
- package/lib/server/openclaw-thinking.js +103 -0
- package/lib/server/openclaw-version.js +1 -1
- package/lib/server/routes/agents.js +10 -3
- package/lib/server/routes/models.js +35 -1
- package/lib/server/routes/onboarding.js +2 -2
- package/lib/server/routes/proxy.js +219 -1
- package/lib/server/routes/system.js +63 -2
- package/lib/server/usage-tracker-config.js +52 -1
- package/lib/server.js +60 -22
- package/package.json +2 -2
|
@@ -1,7 +1,202 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
const http = require("http");
|
|
3
|
+
const https = require("https");
|
|
4
|
+
const { URL } = require("url");
|
|
5
|
+
|
|
6
|
+
const kOpenAiCompatProxyPathPattern =
|
|
7
|
+
/^\/v1\/(?:chat\/completions|responses|embeddings|models(?:\/[^/?#]+)?)$/;
|
|
8
|
+
const kHopByHopResponseHeaders = new Set([
|
|
9
|
+
"connection",
|
|
10
|
+
"keep-alive",
|
|
11
|
+
"proxy-authenticate",
|
|
12
|
+
"proxy-authorization",
|
|
13
|
+
"te",
|
|
14
|
+
"trailer",
|
|
15
|
+
"trailers",
|
|
16
|
+
"transfer-encoding",
|
|
17
|
+
"upgrade",
|
|
18
|
+
]);
|
|
19
|
+
// Strip these even though they're not hop-by-hop: an OpenAI-compatible client
|
|
20
|
+
// (e.g. Sure's external assistant) has no business receiving cookies from the
|
|
21
|
+
// gateway, and a stray Set-Cookie crossing the AlphaClaw boundary would be a
|
|
22
|
+
// real leak.
|
|
23
|
+
const kAlwaysStrippedResponseHeaders = new Set(["set-cookie"]);
|
|
24
|
+
|
|
25
|
+
const extractBearerToken = (authorization) => {
|
|
26
|
+
const match = String(authorization || "").match(/^Bearer\s+(.+)$/i);
|
|
27
|
+
return match ? match[1].trim() : "";
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const getApiAuthThrottleState = (authThrottle, req, now) => {
|
|
31
|
+
if (!authThrottle || typeof authThrottle.getOrCreateLoginAttemptState !== "function") {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const clientKey =
|
|
35
|
+
typeof authThrottle.getClientKey === "function"
|
|
36
|
+
? authThrottle.getClientKey(req)
|
|
37
|
+
: req.ip || req.socket?.remoteAddress || "unknown";
|
|
38
|
+
return {
|
|
39
|
+
clientKey,
|
|
40
|
+
state: authThrottle.getOrCreateLoginAttemptState(clientKey, now),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const sendTooManyAuthAttempts = (res, retryAfterSec = 1) => {
|
|
45
|
+
const normalizedRetryAfterSec = Math.max(1, Math.ceil(Number(retryAfterSec) || 1));
|
|
46
|
+
res.set("Retry-After", String(normalizedRetryAfterSec));
|
|
47
|
+
return res.status(429).json({
|
|
48
|
+
error: "Too many attempts. Try again shortly.",
|
|
49
|
+
retryAfterSec: normalizedRetryAfterSec,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const timingSafeStringEqual = (left, right) => {
|
|
54
|
+
const leftBuffer = Buffer.from(String(left || ""), "utf8");
|
|
55
|
+
const rightBuffer = Buffer.from(String(right || ""), "utf8");
|
|
56
|
+
return (
|
|
57
|
+
leftBuffer.length === rightBuffer.length &&
|
|
58
|
+
crypto.timingSafeEqual(leftBuffer, rightBuffer)
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const extractBodyBuffer = (req) => {
|
|
63
|
+
if (Buffer.isBuffer(req.body)) return req.body;
|
|
64
|
+
if (typeof req.body === "string") return Buffer.from(req.body, "utf8");
|
|
65
|
+
if (req.body && typeof req.body === "object") {
|
|
66
|
+
return Buffer.from(JSON.stringify(req.body), "utf8");
|
|
67
|
+
}
|
|
68
|
+
return Buffer.alloc(0);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const createGatewayProxyHeaders = ({ reqHeaders, bodyBuffer }) => {
|
|
72
|
+
const headers = { ...(reqHeaders || {}) };
|
|
73
|
+
delete headers.host;
|
|
74
|
+
delete headers.connection;
|
|
75
|
+
delete headers["content-length"];
|
|
76
|
+
delete headers["transfer-encoding"];
|
|
77
|
+
// Express has already parsed and (if gzip/deflate) inflated the body, so
|
|
78
|
+
// the bytes we reserialize are plain JSON. Forwarding the original
|
|
79
|
+
// Content-Encoding would tell the gateway to gunzip plain text and fail.
|
|
80
|
+
delete headers["content-encoding"];
|
|
81
|
+
delete headers.cookie;
|
|
82
|
+
if (bodyBuffer.length > 0) {
|
|
83
|
+
headers["content-length"] = String(bodyBuffer.length);
|
|
84
|
+
if (!headers["content-type"]) headers["content-type"] = "application/json";
|
|
85
|
+
}
|
|
86
|
+
return headers;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const proxyOpenAiCompatRequest = ({
|
|
90
|
+
req,
|
|
91
|
+
res,
|
|
92
|
+
getGatewayUrl,
|
|
93
|
+
getGatewayToken,
|
|
94
|
+
openAiCompatApiThrottle,
|
|
95
|
+
}) => {
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const throttleState = getApiAuthThrottleState(
|
|
98
|
+
openAiCompatApiThrottle,
|
|
99
|
+
req,
|
|
100
|
+
now,
|
|
101
|
+
);
|
|
102
|
+
if (
|
|
103
|
+
throttleState &&
|
|
104
|
+
typeof openAiCompatApiThrottle.evaluateLoginThrottle === "function"
|
|
105
|
+
) {
|
|
106
|
+
const throttle = openAiCompatApiThrottle.evaluateLoginThrottle(
|
|
107
|
+
throttleState.state,
|
|
108
|
+
now,
|
|
109
|
+
);
|
|
110
|
+
if (throttle.blocked) {
|
|
111
|
+
return sendTooManyAuthAttempts(res, throttle.retryAfterSec);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const bearerToken = extractBearerToken(req.headers.authorization);
|
|
116
|
+
const expectedGatewayToken = String(getGatewayToken?.() || "").trim();
|
|
117
|
+
if (
|
|
118
|
+
!bearerToken ||
|
|
119
|
+
!expectedGatewayToken ||
|
|
120
|
+
!timingSafeStringEqual(bearerToken, expectedGatewayToken)
|
|
121
|
+
) {
|
|
122
|
+
if (
|
|
123
|
+
throttleState &&
|
|
124
|
+
typeof openAiCompatApiThrottle.recordLoginFailure === "function"
|
|
125
|
+
) {
|
|
126
|
+
const failure = openAiCompatApiThrottle.recordLoginFailure(
|
|
127
|
+
throttleState.state,
|
|
128
|
+
now,
|
|
129
|
+
);
|
|
130
|
+
if (failure.locked) {
|
|
131
|
+
return sendTooManyAuthAttempts(res, failure.retryAfterSec);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return res.status(401).json({ error: "Unauthorized" });
|
|
135
|
+
}
|
|
136
|
+
if (
|
|
137
|
+
throttleState?.clientKey &&
|
|
138
|
+
typeof openAiCompatApiThrottle?.recordLoginSuccess === "function"
|
|
139
|
+
) {
|
|
140
|
+
openAiCompatApiThrottle.recordLoginSuccess(throttleState.clientKey);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let gateway;
|
|
144
|
+
try {
|
|
145
|
+
gateway = new URL(getGatewayUrl());
|
|
146
|
+
} catch {
|
|
147
|
+
return res.status(502).json({ error: "Gateway unavailable" });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const bodyBuffer = extractBodyBuffer(req);
|
|
151
|
+
const protocolClient = gateway.protocol === "https:" ? https : http;
|
|
152
|
+
const headers = createGatewayProxyHeaders({
|
|
153
|
+
reqHeaders: req.headers,
|
|
154
|
+
bodyBuffer,
|
|
155
|
+
});
|
|
156
|
+
headers.authorization = `Bearer ${bearerToken}`;
|
|
157
|
+
|
|
158
|
+
const requestOptions = {
|
|
159
|
+
protocol: gateway.protocol,
|
|
160
|
+
hostname: gateway.hostname,
|
|
161
|
+
port: gateway.port,
|
|
162
|
+
method: req.method,
|
|
163
|
+
path: req.originalUrl || req.url,
|
|
164
|
+
headers,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const proxyReq = protocolClient.request(requestOptions, (proxyRes) => {
|
|
168
|
+
res.statusCode = proxyRes.statusCode || 502;
|
|
169
|
+
for (const [key, value] of Object.entries(proxyRes.headers || {})) {
|
|
170
|
+
if (value == null) continue;
|
|
171
|
+
const lowerKey = key.toLowerCase();
|
|
172
|
+
if (kHopByHopResponseHeaders.has(lowerKey)) continue;
|
|
173
|
+
if (kAlwaysStrippedResponseHeaders.has(lowerKey)) continue;
|
|
174
|
+
res.setHeader(key, value);
|
|
175
|
+
}
|
|
176
|
+
proxyRes.pipe(res);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
proxyReq.on("error", () => {
|
|
180
|
+
if (!res.headersSent) {
|
|
181
|
+
res.status(502).json({ error: "Gateway unavailable" });
|
|
182
|
+
} else {
|
|
183
|
+
res.end();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (bodyBuffer.length > 0) {
|
|
188
|
+
proxyReq.write(bodyBuffer);
|
|
189
|
+
}
|
|
190
|
+
proxyReq.end();
|
|
191
|
+
};
|
|
192
|
+
|
|
1
193
|
const registerProxyRoutes = ({
|
|
2
194
|
app,
|
|
3
195
|
proxy,
|
|
4
196
|
getGatewayUrl,
|
|
197
|
+
getGatewayToken,
|
|
198
|
+
isOpenAiCompatApiEnabled = () => true,
|
|
199
|
+
openAiCompatApiThrottle = null,
|
|
5
200
|
SETUP_API_PREFIXES,
|
|
6
201
|
requireAuth,
|
|
7
202
|
oauthCallbackMiddleware,
|
|
@@ -29,10 +224,33 @@ const registerProxyRoutes = ({
|
|
|
29
224
|
app.all(kHooksPathPattern, webhookMiddleware);
|
|
30
225
|
app.all(kWebhookPathPattern, webhookMiddleware);
|
|
31
226
|
|
|
227
|
+
app.all(kOpenAiCompatProxyPathPattern, (req, res) => {
|
|
228
|
+
if (!isOpenAiCompatApiEnabled()) {
|
|
229
|
+
return res.status(404).json({ error: "Not found" });
|
|
230
|
+
}
|
|
231
|
+
return proxyOpenAiCompatRequest({
|
|
232
|
+
req,
|
|
233
|
+
res,
|
|
234
|
+
getGatewayUrl,
|
|
235
|
+
getGatewayToken,
|
|
236
|
+
openAiCompatApiThrottle,
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
32
240
|
app.all(kApiPathPattern, (req, res, next) => {
|
|
33
241
|
if (SETUP_API_PREFIXES.some((p) => req.path.startsWith(p))) return next();
|
|
34
242
|
proxy.web(req, res, { target: getGatewayUrl() });
|
|
35
243
|
});
|
|
36
244
|
};
|
|
37
245
|
|
|
38
|
-
module.exports = {
|
|
246
|
+
module.exports = {
|
|
247
|
+
kOpenAiCompatProxyPathPattern,
|
|
248
|
+
registerProxyRoutes,
|
|
249
|
+
// Exported for tests.
|
|
250
|
+
__testing: {
|
|
251
|
+
createGatewayProxyHeaders,
|
|
252
|
+
extractBearerToken,
|
|
253
|
+
kHopByHopResponseHeaders,
|
|
254
|
+
kAlwaysStrippedResponseHeaders,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const { buildManagedPaths } = require("../internal-files-migration");
|
|
2
2
|
const { readOpenclawConfig } = require("../openclaw-config");
|
|
3
|
+
const {
|
|
4
|
+
readAlphaclawConfig,
|
|
5
|
+
updateOpenAiCompatApiFeature,
|
|
6
|
+
} = require("../alphaclaw-config");
|
|
3
7
|
const https = require("https");
|
|
4
8
|
|
|
5
9
|
const registerSystemRoutes = ({
|
|
@@ -26,11 +30,13 @@ const registerSystemRoutes = ({
|
|
|
26
30
|
authProfiles,
|
|
27
31
|
watchdog,
|
|
28
32
|
doctorService,
|
|
33
|
+
ensureGatewayProxyConfig,
|
|
34
|
+
getBaseUrl,
|
|
29
35
|
}) => {
|
|
30
36
|
let envRestartPending = false;
|
|
31
37
|
let openclawSecretRuntimePromise = null;
|
|
32
38
|
const kManagedChannelTokenPattern =
|
|
33
|
-
/^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN)(?:_[A-Z0-9_]+)?$/;
|
|
39
|
+
/^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN|WHATSAPP_OWNER_NUMBER)(?:_[A-Z0-9_]+)?$/;
|
|
34
40
|
const kEnvVarsReservedForUserInput = new Set([
|
|
35
41
|
"GITHUB_WORKSPACE_REPO",
|
|
36
42
|
"GOG_KEYRING_PASSWORD",
|
|
@@ -590,6 +596,10 @@ const registerSystemRoutes = ({
|
|
|
590
596
|
repo,
|
|
591
597
|
openclawVersion,
|
|
592
598
|
alphaclawVersion,
|
|
599
|
+
alphaclaw: readAlphaclawConfig({
|
|
600
|
+
fsModule: fs,
|
|
601
|
+
openclawDir: OPENCLAW_DIR,
|
|
602
|
+
}),
|
|
593
603
|
syncCron: getSystemCronStatus(),
|
|
594
604
|
};
|
|
595
605
|
};
|
|
@@ -668,6 +678,57 @@ const registerSystemRoutes = ({
|
|
|
668
678
|
res.json({ ok: true, syncCron: status });
|
|
669
679
|
});
|
|
670
680
|
|
|
681
|
+
app.get("/api/alphaclaw/config", (req, res) => {
|
|
682
|
+
res.json({
|
|
683
|
+
ok: true,
|
|
684
|
+
config: readAlphaclawConfig({
|
|
685
|
+
fsModule: fs,
|
|
686
|
+
openclawDir: OPENCLAW_DIR,
|
|
687
|
+
}),
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
app.put("/api/alphaclaw/config/features/openai-compat-api", async (req, res) => {
|
|
692
|
+
const { enabled } = req.body || {};
|
|
693
|
+
if (typeof enabled !== "boolean") {
|
|
694
|
+
return res
|
|
695
|
+
.status(400)
|
|
696
|
+
.json({ ok: false, error: "enabled must be a boolean" });
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
try {
|
|
700
|
+
const { config, changed } = updateOpenAiCompatApiFeature({
|
|
701
|
+
fsModule: fs,
|
|
702
|
+
openclawDir: OPENCLAW_DIR,
|
|
703
|
+
enabled,
|
|
704
|
+
});
|
|
705
|
+
let gatewayConfigChanged = false;
|
|
706
|
+
if (enabled && isOnboarded() && typeof ensureGatewayProxyConfig === "function") {
|
|
707
|
+
gatewayConfigChanged = ensureGatewayProxyConfig(getBaseUrl?.(req));
|
|
708
|
+
if (gatewayConfigChanged && restartRequiredState?.markRequired) {
|
|
709
|
+
restartRequiredState.markRequired("openai_compat_api_enabled");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const snapshot =
|
|
713
|
+
typeof restartRequiredState?.getSnapshot === "function"
|
|
714
|
+
? await restartRequiredState.getSnapshot()
|
|
715
|
+
: null;
|
|
716
|
+
res.json({
|
|
717
|
+
ok: true,
|
|
718
|
+
changed,
|
|
719
|
+
gatewayConfigChanged,
|
|
720
|
+
config,
|
|
721
|
+
restartRequired:
|
|
722
|
+
Boolean(snapshot?.restartRequired) || (envRestartPending && isOnboarded()),
|
|
723
|
+
});
|
|
724
|
+
} catch (err) {
|
|
725
|
+
res.status(500).json({
|
|
726
|
+
ok: false,
|
|
727
|
+
error: err.message || "Could not update AlphaClaw config",
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
|
|
671
732
|
app.get("/api/alphaclaw/version", async (req, res) => {
|
|
672
733
|
const refresh = String(req.query.refresh || "") === "1";
|
|
673
734
|
const status = await alphaclawVersionService.getVersionStatus(refresh);
|
|
@@ -838,7 +899,7 @@ const registerSystemRoutes = ({
|
|
|
838
899
|
}
|
|
839
900
|
restartRequiredState.markRestartInProgress();
|
|
840
901
|
try {
|
|
841
|
-
restartGateway();
|
|
902
|
+
await restartGateway();
|
|
842
903
|
envRestartPending = false;
|
|
843
904
|
restartRequiredState.clearRequired();
|
|
844
905
|
restartRequiredState.markRestartComplete();
|
|
@@ -8,6 +8,8 @@ const kUsageTrackerPluginPath = path.resolve(
|
|
|
8
8
|
"usage-tracker",
|
|
9
9
|
);
|
|
10
10
|
const kConversationAccessHookPolicyKey = "allowConversationAccess";
|
|
11
|
+
const kChannelPluginIds = ["telegram", "discord", "slack", "whatsapp"];
|
|
12
|
+
const kDefaultDiscordGroupPolicy = "disabled";
|
|
11
13
|
|
|
12
14
|
const ensurePluginsShell = (cfg = {}) => {
|
|
13
15
|
if (!cfg.plugins || typeof cfg.plugins !== "object") cfg.plugins = {};
|
|
@@ -70,13 +72,58 @@ const ensureUsageTrackerPluginEntry = (cfg = {}) => {
|
|
|
70
72
|
return JSON.stringify(cfg) !== before;
|
|
71
73
|
};
|
|
72
74
|
|
|
75
|
+
const hasDiscordGuildAllowlist = (discordConfig = {}) => {
|
|
76
|
+
const guilds = discordConfig.guilds;
|
|
77
|
+
return !!guilds && typeof guilds === "object" && Object.keys(guilds).length > 0;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const reconcileDiscordGroupPolicy = (cfg = {}) => {
|
|
81
|
+
const discord = cfg.channels?.discord;
|
|
82
|
+
if (!discord || typeof discord !== "object" || discord.enabled === false) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (hasDiscordGuildAllowlist(discord)) return false;
|
|
86
|
+
if (discord.groupPolicy !== "allowlist") return false;
|
|
87
|
+
discord.groupPolicy = kDefaultDiscordGroupPolicy;
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const reconcileEnabledChannelPlugins = (cfg = {}) => {
|
|
92
|
+
ensurePluginsShell(cfg);
|
|
93
|
+
let changed = false;
|
|
94
|
+
for (const pluginKey of kChannelPluginIds) {
|
|
95
|
+
const channelConfig = cfg.channels?.[pluginKey];
|
|
96
|
+
if (!channelConfig || typeof channelConfig !== "object") continue;
|
|
97
|
+
if (channelConfig.enabled !== true) continue;
|
|
98
|
+
const allowBefore = cfg.plugins.allow.length;
|
|
99
|
+
ensurePluginAllowed({ cfg, pluginKey });
|
|
100
|
+
if (cfg.plugins.allow.length > allowBefore) changed = true;
|
|
101
|
+
const existingEntry = cfg.plugins.entries[pluginKey];
|
|
102
|
+
if (!existingEntry || existingEntry.enabled !== true) {
|
|
103
|
+
cfg.plugins.entries[pluginKey] = {
|
|
104
|
+
...(existingEntry && typeof existingEntry === "object" ? existingEntry : {}),
|
|
105
|
+
enabled: true,
|
|
106
|
+
};
|
|
107
|
+
changed = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return changed;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const reconcileManagedPluginConfig = (cfg = {}) => {
|
|
114
|
+
let changed = ensureUsageTrackerPluginEntry(cfg);
|
|
115
|
+
if (reconcileEnabledChannelPlugins(cfg)) changed = true;
|
|
116
|
+
if (reconcileDiscordGroupPolicy(cfg)) changed = true;
|
|
117
|
+
return changed;
|
|
118
|
+
};
|
|
119
|
+
|
|
73
120
|
const ensureUsageTrackerPluginConfig = ({ fsModule, openclawDir }) => {
|
|
74
121
|
const cfg = readOpenclawConfig({
|
|
75
122
|
fsModule,
|
|
76
123
|
openclawDir,
|
|
77
124
|
fallback: {},
|
|
78
125
|
});
|
|
79
|
-
const changed =
|
|
126
|
+
const changed = reconcileManagedPluginConfig(cfg);
|
|
80
127
|
if (!changed) return false;
|
|
81
128
|
writeOpenclawConfig({
|
|
82
129
|
fsModule,
|
|
@@ -89,8 +136,12 @@ const ensureUsageTrackerPluginConfig = ({ fsModule, openclawDir }) => {
|
|
|
89
136
|
|
|
90
137
|
module.exports = {
|
|
91
138
|
kUsageTrackerPluginPath,
|
|
139
|
+
kDefaultDiscordGroupPolicy,
|
|
92
140
|
ensurePluginsShell,
|
|
93
141
|
ensurePluginAllowed,
|
|
94
142
|
ensureUsageTrackerPluginEntry,
|
|
143
|
+
reconcileDiscordGroupPolicy,
|
|
144
|
+
reconcileEnabledChannelPlugins,
|
|
145
|
+
reconcileManagedPluginConfig,
|
|
95
146
|
ensureUsageTrackerPluginConfig,
|
|
96
147
|
};
|
package/lib/server.js
CHANGED
|
@@ -65,6 +65,10 @@ const {
|
|
|
65
65
|
getDoctorCard,
|
|
66
66
|
updateDoctorCardStatus,
|
|
67
67
|
} = require("./server/db/doctor");
|
|
68
|
+
const {
|
|
69
|
+
initAuthDb,
|
|
70
|
+
createLoginThrottleStore,
|
|
71
|
+
} = require("./server/db/auth");
|
|
68
72
|
const { createWebhookMiddleware } = require("./server/webhook-middleware");
|
|
69
73
|
const {
|
|
70
74
|
readEnvFile,
|
|
@@ -144,6 +148,9 @@ const {
|
|
|
144
148
|
const {
|
|
145
149
|
ensureOpenclawStartupEnv,
|
|
146
150
|
} = require("./server/openclaw-runtime-env");
|
|
151
|
+
const {
|
|
152
|
+
isOpenAiCompatApiEnabled,
|
|
153
|
+
} = require("./server/alphaclaw-config");
|
|
147
154
|
|
|
148
155
|
const { PORT, kTrustProxyHops, SETUP_API_PREFIXES } = constants;
|
|
149
156
|
|
|
@@ -161,6 +168,18 @@ const app = express();
|
|
|
161
168
|
app.set("trust proxy", kTrustProxyHops);
|
|
162
169
|
app.use(["/webhook", "/hooks"], express.raw({ type: "*/*", limit: "5mb" }));
|
|
163
170
|
app.use("/gmail-pubsub", express.raw({ type: "*/*", limit: "5mb" }));
|
|
171
|
+
const openAiCompatJsonParser = express.json({ limit: "50mb" });
|
|
172
|
+
const isOpenAiCompatApiCurrentlyEnabled = () =>
|
|
173
|
+
isOpenAiCompatApiEnabled({
|
|
174
|
+
fsModule: fs,
|
|
175
|
+
openclawDir: constants.OPENCLAW_DIR,
|
|
176
|
+
});
|
|
177
|
+
app.use("/v1", (req, res, next) => {
|
|
178
|
+
if (!isOpenAiCompatApiCurrentlyEnabled()) {
|
|
179
|
+
return res.status(404).json({ error: "Not found" });
|
|
180
|
+
}
|
|
181
|
+
return openAiCompatJsonParser(req, res, next);
|
|
182
|
+
});
|
|
164
183
|
app.use(express.json({ limit: "5mb" }));
|
|
165
184
|
|
|
166
185
|
const proxy = httpProxy.createProxyServer({
|
|
@@ -186,7 +205,27 @@ const agentsService = createAgentsService({
|
|
|
186
205
|
restartGateway: () => restartGatewayWithReload(reloadEnv),
|
|
187
206
|
clawCmd,
|
|
188
207
|
});
|
|
189
|
-
const
|
|
208
|
+
const loginThrottleStore = createLoginThrottleStore();
|
|
209
|
+
const loginThrottle = {
|
|
210
|
+
...createLoginThrottle({ store: loginThrottleStore }),
|
|
211
|
+
getClientKey,
|
|
212
|
+
};
|
|
213
|
+
const openAiCompatApiThrottle = {
|
|
214
|
+
...createLoginThrottle({
|
|
215
|
+
store: loginThrottleStore,
|
|
216
|
+
scope: "openai-compat-api",
|
|
217
|
+
windowMs: constants.kOpenAiCompatApiRateWindowMs,
|
|
218
|
+
maxAttempts: constants.kOpenAiCompatApiRateMaxAttempts,
|
|
219
|
+
baseLockMs: constants.kOpenAiCompatApiRateBaseLockMs,
|
|
220
|
+
maxLockMs: constants.kOpenAiCompatApiRateMaxLockMs,
|
|
221
|
+
globalWindowMs: constants.kOpenAiCompatApiRateGlobalWindowMs,
|
|
222
|
+
globalMaxAttempts: constants.kOpenAiCompatApiRateGlobalMaxAttempts,
|
|
223
|
+
globalBaseLockMs: constants.kOpenAiCompatApiRateGlobalBaseLockMs,
|
|
224
|
+
globalMaxLockMs: constants.kOpenAiCompatApiRateGlobalMaxLockMs,
|
|
225
|
+
stateTtlMs: constants.kOpenAiCompatApiRateStateTtlMs,
|
|
226
|
+
}),
|
|
227
|
+
getClientKey,
|
|
228
|
+
};
|
|
190
229
|
const resolveSetupUrl = () =>
|
|
191
230
|
resolveSetupUiUrl(
|
|
192
231
|
process.env.ALPHACLAW_SETUP_URL ||
|
|
@@ -219,6 +258,7 @@ const cronService = createCronService({
|
|
|
219
258
|
app.use(express.static(path.join(__dirname, "public")));
|
|
220
259
|
initializeServerDatabases({
|
|
221
260
|
constants,
|
|
261
|
+
initAuthDb,
|
|
222
262
|
initWebhooksDb,
|
|
223
263
|
initWatchdogDb,
|
|
224
264
|
initUsageDb,
|
|
@@ -286,7 +326,11 @@ const doSyncPromptFiles = () => {
|
|
|
286
326
|
});
|
|
287
327
|
installGogCliSkill({ fs, openclawDir: constants.OPENCLAW_DIR });
|
|
288
328
|
};
|
|
289
|
-
const {
|
|
329
|
+
const {
|
|
330
|
+
isAuthorizedRequest,
|
|
331
|
+
gmailWatchService,
|
|
332
|
+
runOnboardedBootSequence: runOnboardedBoot,
|
|
333
|
+
} = registerServerRoutes({
|
|
290
334
|
app,
|
|
291
335
|
fs,
|
|
292
336
|
constants,
|
|
@@ -306,8 +350,21 @@ const { isAuthorizedRequest, gmailWatchService } = registerServerRoutes({
|
|
|
306
350
|
resolveGithubRepoUrl,
|
|
307
351
|
resolveModelProvider,
|
|
308
352
|
ensureGatewayProxyConfig,
|
|
353
|
+
isOpenAiCompatApiEnabled: isOpenAiCompatApiCurrentlyEnabled,
|
|
354
|
+
openAiCompatApiThrottle,
|
|
309
355
|
getBaseUrl,
|
|
310
356
|
startGateway,
|
|
357
|
+
ensureManagedExecDefaults: () =>
|
|
358
|
+
ensureManagedExecDefaults({
|
|
359
|
+
fsModule: fs,
|
|
360
|
+
openclawDir: constants.OPENCLAW_DIR,
|
|
361
|
+
}),
|
|
362
|
+
ensureUsageTrackerPluginConfig: () =>
|
|
363
|
+
ensureUsageTrackerPluginConfig({
|
|
364
|
+
fsModule: fs,
|
|
365
|
+
openclawDir: constants.OPENCLAW_DIR,
|
|
366
|
+
}),
|
|
367
|
+
resolveSetupUrl,
|
|
311
368
|
syncChannelConfig,
|
|
312
369
|
getChannelStatus,
|
|
313
370
|
openclawVersionService,
|
|
@@ -393,26 +450,7 @@ startServerLifecycle({
|
|
|
393
450
|
server,
|
|
394
451
|
PORT,
|
|
395
452
|
isOnboarded,
|
|
396
|
-
runOnboardedBootSequence,
|
|
397
|
-
ensureManagedExecDefaults: () =>
|
|
398
|
-
ensureManagedExecDefaults({
|
|
399
|
-
fsModule: fs,
|
|
400
|
-
openclawDir: constants.OPENCLAW_DIR,
|
|
401
|
-
}),
|
|
402
|
-
ensureUsageTrackerPluginConfig: () =>
|
|
403
|
-
ensureUsageTrackerPluginConfig({
|
|
404
|
-
fsModule: fs,
|
|
405
|
-
openclawDir: constants.OPENCLAW_DIR,
|
|
406
|
-
}),
|
|
407
|
-
doSyncPromptFiles,
|
|
408
|
-
reloadEnv,
|
|
409
|
-
syncChannelConfig,
|
|
410
|
-
readEnvFile,
|
|
411
|
-
ensureGatewayProxyConfig,
|
|
412
|
-
resolveSetupUrl,
|
|
413
|
-
startGateway,
|
|
414
|
-
watchdog,
|
|
415
|
-
gmailWatchService,
|
|
453
|
+
runOnboardedBootSequence: runOnboardedBoot,
|
|
416
454
|
});
|
|
417
455
|
registerServerShutdown({
|
|
418
456
|
gmailWatchService,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrysb/alphaclaw",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.18",
|
|
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.5.
|
|
36
|
+
"openclaw": "2026.5.28",
|
|
37
37
|
"ws": "^8.19.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|