@chrysb/alphaclaw 0.9.16 → 0.9.17
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/css/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +1519 -1459
- 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/envars.js +1 -1
- package/lib/public/js/components/row-accessory-select.js +52 -0
- package/lib/public/js/lib/api.js +7 -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/thinking-levels.js +37 -0
- package/lib/server/agents/agents.js +33 -7
- package/lib/server/agents/channels.js +4 -2
- package/lib/server/chat-ws.js +4 -1
- package/lib/server/constants.js +25 -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 +158 -19
- package/lib/server/helpers.js +1 -3
- package/lib/server/init/register-server-routes.js +37 -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 +242 -60
- package/lib/server/model-catalog-bootstrap.json +5 -0
- package/lib/server/onboarding/index.js +2 -2
- 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/system.js +2 -2
- package/lib/server/usage-tracker-config.js +52 -1
- package/lib/server.js +26 -22
- package/package.json +2 -2
|
@@ -1,77 +1,255 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const next = {
|
|
13
|
-
attempts: 0,
|
|
14
|
-
windowStart: now,
|
|
15
|
-
lockUntil: 0,
|
|
16
|
-
failStreak: 0,
|
|
17
|
-
lastSeenAt: now,
|
|
18
|
-
};
|
|
19
|
-
kLoginAttemptStates.set(clientKey, next);
|
|
20
|
-
return next;
|
|
21
|
-
};
|
|
1
|
+
const {
|
|
2
|
+
kLoginWindowMs,
|
|
3
|
+
kLoginMaxAttempts,
|
|
4
|
+
kLoginBaseLockMs,
|
|
5
|
+
kLoginMaxLockMs,
|
|
6
|
+
kLoginGlobalWindowMs,
|
|
7
|
+
kLoginGlobalMaxAttempts,
|
|
8
|
+
kLoginGlobalBaseLockMs,
|
|
9
|
+
kLoginGlobalMaxLockMs,
|
|
10
|
+
kLoginStateTtlMs,
|
|
11
|
+
} = require("./constants");
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
13
|
+
const kGlobalStateKey = "global:login";
|
|
14
|
+
|
|
15
|
+
const createLoginAttemptState = (now) => ({
|
|
16
|
+
attempts: 0,
|
|
17
|
+
windowStart: now,
|
|
18
|
+
lockUntil: 0,
|
|
19
|
+
failStreak: 0,
|
|
20
|
+
lastSeenAt: now,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const normalizeState = (state, now) => ({
|
|
24
|
+
attempts: Number.parseInt(String(state?.attempts ?? 0), 10) || 0,
|
|
25
|
+
windowStart: Number.parseInt(String(state?.windowStart ?? now), 10) || now,
|
|
26
|
+
lockUntil: Number.parseInt(String(state?.lockUntil ?? 0), 10) || 0,
|
|
27
|
+
failStreak: Number.parseInt(String(state?.failStreak ?? 0), 10) || 0,
|
|
28
|
+
lastSeenAt: Number.parseInt(String(state?.lastSeenAt ?? now), 10) || now,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const createMemoryLoginThrottleStore = () => {
|
|
32
|
+
const states = new Map();
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
get: (stateKey) => states.get(stateKey) || null,
|
|
36
|
+
set: (stateKey, state) => {
|
|
37
|
+
states.set(stateKey, { ...state });
|
|
38
|
+
},
|
|
39
|
+
delete: (stateKey) => {
|
|
40
|
+
states.delete(stateKey);
|
|
41
|
+
},
|
|
42
|
+
entries: () =>
|
|
43
|
+
Array.from(states.entries()).map(([stateKey, state]) => [
|
|
44
|
+
stateKey,
|
|
45
|
+
{ ...state },
|
|
46
|
+
]),
|
|
47
|
+
runExclusive: (callback) => callback(),
|
|
36
48
|
};
|
|
49
|
+
};
|
|
37
50
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
state
|
|
51
|
+
const getClientStateKey = (clientKey) =>
|
|
52
|
+
`client:${String(clientKey || "unknown")}`;
|
|
53
|
+
|
|
54
|
+
const getOrCreateState = (store, stateKey, now) => {
|
|
55
|
+
const existing = store.get(stateKey);
|
|
56
|
+
if (existing) {
|
|
57
|
+
const state = normalizeState(existing, now);
|
|
45
58
|
state.lastSeenAt = now;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
store.set(stateKey, state);
|
|
60
|
+
return state;
|
|
61
|
+
}
|
|
62
|
+
const next = createLoginAttemptState(now);
|
|
63
|
+
store.set(stateKey, next);
|
|
64
|
+
return next;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const evaluateState = ({ store, stateKey, now, windowMs }) => {
|
|
68
|
+
const state = getOrCreateState(store, stateKey, now);
|
|
69
|
+
if (state.lockUntil > now) {
|
|
70
|
+
return {
|
|
71
|
+
state,
|
|
72
|
+
blocked: true,
|
|
73
|
+
retryAfterSec: Math.max(1, Math.ceil((state.lockUntil - now) / 1000)),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (now - state.windowStart >= windowMs) {
|
|
77
|
+
state.attempts = 0;
|
|
78
|
+
state.windowStart = now;
|
|
79
|
+
store.set(stateKey, state);
|
|
80
|
+
}
|
|
81
|
+
return { state, blocked: false, retryAfterSec: 0 };
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const recordStateFailure = ({
|
|
85
|
+
store,
|
|
86
|
+
stateKey,
|
|
87
|
+
now,
|
|
88
|
+
windowMs,
|
|
89
|
+
maxAttempts,
|
|
90
|
+
baseLockMs,
|
|
91
|
+
maxLockMs,
|
|
92
|
+
}) => {
|
|
93
|
+
const state = getOrCreateState(store, stateKey, now);
|
|
94
|
+
if (state.lockUntil > now) {
|
|
95
|
+
return {
|
|
96
|
+
state,
|
|
97
|
+
lockMs: state.lockUntil - now,
|
|
98
|
+
locked: true,
|
|
99
|
+
retryAfterSec: Math.max(1, Math.ceil((state.lockUntil - now) / 1000)),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (now - state.windowStart >= windowMs) {
|
|
50
103
|
state.attempts = 0;
|
|
51
104
|
state.windowStart = now;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
105
|
+
}
|
|
106
|
+
state.attempts += 1;
|
|
107
|
+
state.lastSeenAt = now;
|
|
108
|
+
if (state.attempts < maxAttempts) {
|
|
109
|
+
store.set(stateKey, state);
|
|
110
|
+
return { state, lockMs: 0, locked: false, retryAfterSec: 0 };
|
|
111
|
+
}
|
|
112
|
+
state.failStreak += 1;
|
|
113
|
+
state.attempts = 0;
|
|
114
|
+
state.windowStart = now;
|
|
115
|
+
const lockMultiplier = Math.max(1, 2 ** (state.failStreak - 1));
|
|
116
|
+
const lockMs = Math.min(baseLockMs * lockMultiplier, maxLockMs);
|
|
117
|
+
state.lockUntil = now + lockMs;
|
|
118
|
+
store.set(stateKey, state);
|
|
119
|
+
return {
|
|
120
|
+
state,
|
|
121
|
+
lockMs,
|
|
122
|
+
locked: true,
|
|
123
|
+
retryAfterSec: Math.max(1, Math.ceil(lockMs / 1000)),
|
|
56
124
|
};
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const chooseThrottleResult = (...results) => {
|
|
128
|
+
const blockedResults = results.filter((result) => result.blocked);
|
|
129
|
+
if (blockedResults.length === 0) {
|
|
130
|
+
return { blocked: false, retryAfterSec: 0 };
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
blocked: true,
|
|
134
|
+
retryAfterSec: Math.max(
|
|
135
|
+
...blockedResults.map((result) => result.retryAfterSec || 0),
|
|
136
|
+
),
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const chooseFailureResult = (...results) => {
|
|
141
|
+
const lockedResults = results.filter((result) => result.locked);
|
|
142
|
+
if (lockedResults.length === 0) {
|
|
143
|
+
return { lockMs: 0, locked: false, retryAfterSec: 0 };
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
lockMs: Math.max(...lockedResults.map((result) => result.lockMs || 0)),
|
|
147
|
+
locked: true,
|
|
148
|
+
retryAfterSec: Math.max(
|
|
149
|
+
...lockedResults.map((result) => result.retryAfterSec || 0),
|
|
150
|
+
),
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const createLoginThrottle = ({
|
|
155
|
+
store = createMemoryLoginThrottleStore(),
|
|
156
|
+
} = {}) => {
|
|
157
|
+
const runExclusive =
|
|
158
|
+
typeof store.runExclusive === "function"
|
|
159
|
+
? (callback) => store.runExclusive(callback)
|
|
160
|
+
: (callback) => callback();
|
|
161
|
+
|
|
162
|
+
const getOrCreateLoginAttemptState = (clientKey, now) =>
|
|
163
|
+
runExclusive(() => {
|
|
164
|
+
const clientStateKey = getClientStateKey(clientKey);
|
|
165
|
+
return {
|
|
166
|
+
clientKey,
|
|
167
|
+
clientStateKey,
|
|
168
|
+
globalStateKey: kGlobalStateKey,
|
|
169
|
+
client: getOrCreateState(store, clientStateKey, now),
|
|
170
|
+
global: getOrCreateState(store, kGlobalStateKey, now),
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const evaluateLoginThrottle = (stateBundle, now) =>
|
|
175
|
+
runExclusive(() => {
|
|
176
|
+
const clientStateKey =
|
|
177
|
+
stateBundle?.clientStateKey ||
|
|
178
|
+
getClientStateKey(stateBundle?.clientKey);
|
|
179
|
+
const globalStateKey = stateBundle?.globalStateKey || kGlobalStateKey;
|
|
180
|
+
const clientResult = evaluateState({
|
|
181
|
+
store,
|
|
182
|
+
stateKey: clientStateKey,
|
|
183
|
+
now,
|
|
184
|
+
windowMs: kLoginWindowMs,
|
|
185
|
+
});
|
|
186
|
+
const globalResult = evaluateState({
|
|
187
|
+
store,
|
|
188
|
+
stateKey: globalStateKey,
|
|
189
|
+
now,
|
|
190
|
+
windowMs: kLoginGlobalWindowMs,
|
|
191
|
+
});
|
|
192
|
+
if (stateBundle) {
|
|
193
|
+
stateBundle.client = clientResult.state;
|
|
194
|
+
stateBundle.global = globalResult.state;
|
|
195
|
+
}
|
|
196
|
+
return chooseThrottleResult(clientResult, globalResult);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const recordLoginFailure = (stateBundle, now) =>
|
|
200
|
+
runExclusive(() => {
|
|
201
|
+
const clientStateKey =
|
|
202
|
+
stateBundle?.clientStateKey ||
|
|
203
|
+
getClientStateKey(stateBundle?.clientKey);
|
|
204
|
+
const globalStateKey = stateBundle?.globalStateKey || kGlobalStateKey;
|
|
205
|
+
const clientResult = recordStateFailure({
|
|
206
|
+
store,
|
|
207
|
+
stateKey: clientStateKey,
|
|
208
|
+
now,
|
|
209
|
+
windowMs: kLoginWindowMs,
|
|
210
|
+
maxAttempts: kLoginMaxAttempts,
|
|
211
|
+
baseLockMs: kLoginBaseLockMs,
|
|
212
|
+
maxLockMs: kLoginMaxLockMs,
|
|
213
|
+
});
|
|
214
|
+
const globalResult = recordStateFailure({
|
|
215
|
+
store,
|
|
216
|
+
stateKey: globalStateKey,
|
|
217
|
+
now,
|
|
218
|
+
windowMs: kLoginGlobalWindowMs,
|
|
219
|
+
maxAttempts: kLoginGlobalMaxAttempts,
|
|
220
|
+
baseLockMs: kLoginGlobalBaseLockMs,
|
|
221
|
+
maxLockMs: kLoginGlobalMaxLockMs,
|
|
222
|
+
});
|
|
223
|
+
if (stateBundle) {
|
|
224
|
+
stateBundle.client = clientResult.state;
|
|
225
|
+
stateBundle.global = globalResult.state;
|
|
226
|
+
}
|
|
227
|
+
return chooseFailureResult(clientResult, globalResult);
|
|
228
|
+
});
|
|
57
229
|
|
|
58
230
|
const recordLoginSuccess = (clientKey) => {
|
|
59
231
|
if (!clientKey) return;
|
|
60
|
-
|
|
232
|
+
runExclusive(() => {
|
|
233
|
+
store.delete(getClientStateKey(clientKey));
|
|
234
|
+
store.delete(kGlobalStateKey);
|
|
235
|
+
});
|
|
61
236
|
};
|
|
62
237
|
|
|
63
238
|
const cleanupLoginAttemptStates = () => {
|
|
64
239
|
const now = Date.now();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
240
|
+
runExclusive(() => {
|
|
241
|
+
for (const [stateKey, rawState] of store.entries()) {
|
|
242
|
+
if (!rawState) {
|
|
243
|
+
store.delete(stateKey);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const state = normalizeState(rawState, now);
|
|
247
|
+
if (state.lockUntil > now) continue;
|
|
248
|
+
if (now - state.lastSeenAt > kLoginStateTtlMs) {
|
|
249
|
+
store.delete(stateKey);
|
|
250
|
+
}
|
|
69
251
|
}
|
|
70
|
-
|
|
71
|
-
if (now - state.lastSeenAt > kLoginStateTtlMs) {
|
|
72
|
-
kLoginAttemptStates.delete(key);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
252
|
+
});
|
|
75
253
|
};
|
|
76
254
|
|
|
77
255
|
return {
|
|
@@ -83,4 +261,8 @@ const createLoginThrottle = () => {
|
|
|
83
261
|
};
|
|
84
262
|
};
|
|
85
263
|
|
|
86
|
-
module.exports = {
|
|
264
|
+
module.exports = {
|
|
265
|
+
createLoginThrottle,
|
|
266
|
+
createMemoryLoginThrottleStore,
|
|
267
|
+
kGlobalStateKey,
|
|
268
|
+
};
|
|
@@ -554,6 +554,11 @@
|
|
|
554
554
|
"provider": "anthropic",
|
|
555
555
|
"label": "Claude Opus 4.6"
|
|
556
556
|
},
|
|
557
|
+
{
|
|
558
|
+
"key": "anthropic/claude-opus-4-8",
|
|
559
|
+
"provider": "anthropic",
|
|
560
|
+
"label": "Claude Opus 4.8"
|
|
561
|
+
},
|
|
557
562
|
{
|
|
558
563
|
"key": "anthropic/claude-opus-4-7",
|
|
559
564
|
"provider": "anthropic",
|
|
@@ -327,7 +327,7 @@ const createOnboardingService = ({
|
|
|
327
327
|
authProfiles,
|
|
328
328
|
ensureGatewayProxyConfig,
|
|
329
329
|
getBaseUrl,
|
|
330
|
-
|
|
330
|
+
runOnboardedBootSequence,
|
|
331
331
|
}) => {
|
|
332
332
|
const { OPENCLAW_DIR, WORKSPACE_DIR, kOnboardingMarkerPath } = constants;
|
|
333
333
|
|
|
@@ -567,7 +567,7 @@ const createOnboardingService = ({
|
|
|
567
567
|
console.error("[onboard] Git push error:", e.message);
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
-
|
|
570
|
+
runOnboardedBootSequence();
|
|
571
571
|
return { status: 200, body: { ok: true } };
|
|
572
572
|
};
|
|
573
573
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { pathToFileURL } = require("url");
|
|
4
|
+
|
|
5
|
+
const kThinkingModuleSentinel = "listThinkingLevelOptions";
|
|
6
|
+
|
|
7
|
+
let thinkingModulePromise = null;
|
|
8
|
+
|
|
9
|
+
const resolveOpenclawDistDir = () => path.dirname(require.resolve("openclaw"));
|
|
10
|
+
|
|
11
|
+
const resolveThinkingModulePath = (distDir = resolveOpenclawDistDir()) => {
|
|
12
|
+
for (const name of fs.readdirSync(distDir)) {
|
|
13
|
+
if (!/^thinking-.*\.js$/.test(name)) continue;
|
|
14
|
+
if (name.includes("api") || name.includes("policy")) continue;
|
|
15
|
+
const fullPath = path.join(distDir, name);
|
|
16
|
+
const source = fs.readFileSync(fullPath, "utf8");
|
|
17
|
+
if (source.includes(kThinkingModuleSentinel)) return fullPath;
|
|
18
|
+
}
|
|
19
|
+
throw new Error("OpenClaw thinking module not found");
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const loadThinkingModule = async () => {
|
|
23
|
+
if (!thinkingModulePromise) {
|
|
24
|
+
const modulePath = resolveThinkingModulePath();
|
|
25
|
+
thinkingModulePromise = import(pathToFileURL(modulePath).href);
|
|
26
|
+
}
|
|
27
|
+
return thinkingModulePromise;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const splitModelKey = (modelKey = "") => {
|
|
31
|
+
const normalized = String(modelKey || "").trim();
|
|
32
|
+
const slashIndex = normalized.indexOf("/");
|
|
33
|
+
if (slashIndex <= 0) return { provider: "", model: normalized };
|
|
34
|
+
return {
|
|
35
|
+
provider: normalized.slice(0, slashIndex),
|
|
36
|
+
model: normalized.slice(slashIndex + 1),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const buildCatalogEntry = ({ provider, model, reasoning, compat } = {}) => {
|
|
41
|
+
const normalizedProvider = String(provider || "").trim();
|
|
42
|
+
const normalizedModel = String(model || "").trim();
|
|
43
|
+
if (!normalizedProvider || !normalizedModel) return null;
|
|
44
|
+
const entry = {
|
|
45
|
+
provider: normalizedProvider,
|
|
46
|
+
id: normalizedModel,
|
|
47
|
+
};
|
|
48
|
+
if (typeof reasoning === "boolean") entry.reasoning = reasoning;
|
|
49
|
+
if (compat && typeof compat === "object") entry.compat = compat;
|
|
50
|
+
return entry;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const resolveThinkingApi = async () => {
|
|
54
|
+
const mod = await loadThinkingModule();
|
|
55
|
+
return {
|
|
56
|
+
listThinkingLevelOptions: mod.listThinkingLevelOptions || mod.i,
|
|
57
|
+
resolveThinkingDefaultForModel: mod.resolveThinkingDefaultForModel || mod.s,
|
|
58
|
+
normalizeThinkLevel: mod.normalizeThinkLevel || mod.p,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const resolveThinkingOptionsForModel = async ({
|
|
63
|
+
modelKey = "",
|
|
64
|
+
catalog = [],
|
|
65
|
+
} = {}) => {
|
|
66
|
+
const { provider, model } = splitModelKey(modelKey);
|
|
67
|
+
if (!provider || !model) {
|
|
68
|
+
return {
|
|
69
|
+
levels: [],
|
|
70
|
+
modelDefault: "off",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const api = await resolveThinkingApi();
|
|
74
|
+
const levels = api.listThinkingLevelOptions(provider, model, catalog) || [];
|
|
75
|
+
const modelDefault =
|
|
76
|
+
api.resolveThinkingDefaultForModel({
|
|
77
|
+
provider,
|
|
78
|
+
model,
|
|
79
|
+
catalog,
|
|
80
|
+
}) || "off";
|
|
81
|
+
return {
|
|
82
|
+
levels: levels.map((entry) => ({
|
|
83
|
+
id: String(entry?.id || "").trim(),
|
|
84
|
+
label: String(entry?.label || entry?.id || "").trim(),
|
|
85
|
+
})),
|
|
86
|
+
modelDefault: String(modelDefault || "off").trim() || "off",
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const normalizeThinkingDefaultValue = async (raw) => {
|
|
91
|
+
if (raw === null || raw === undefined || raw === "") return null;
|
|
92
|
+
const api = await resolveThinkingApi();
|
|
93
|
+
const normalized = api.normalizeThinkLevel(String(raw || "").trim());
|
|
94
|
+
return normalized || null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
buildCatalogEntry,
|
|
99
|
+
loadThinkingModule,
|
|
100
|
+
normalizeThinkingDefaultValue,
|
|
101
|
+
resolveThinkingOptionsForModel,
|
|
102
|
+
splitModelKey,
|
|
103
|
+
};
|
|
@@ -178,7 +178,11 @@ const registerAgentRoutes = ({
|
|
|
178
178
|
|
|
179
179
|
app.get("/api/agents", (_req, res) => {
|
|
180
180
|
try {
|
|
181
|
-
res.json({
|
|
181
|
+
res.json({
|
|
182
|
+
ok: true,
|
|
183
|
+
agents: agentsService.listAgents(),
|
|
184
|
+
defaults: agentsService.getAgentDefaults?.() || {},
|
|
185
|
+
});
|
|
182
186
|
} catch (error) {
|
|
183
187
|
res.status(500).json({ ok: false, error: error.message });
|
|
184
188
|
}
|
|
@@ -237,9 +241,12 @@ const registerAgentRoutes = ({
|
|
|
237
241
|
}
|
|
238
242
|
});
|
|
239
243
|
|
|
240
|
-
app.put("/api/agents/:id", (req, res) => {
|
|
244
|
+
app.put("/api/agents/:id", async (req, res) => {
|
|
241
245
|
try {
|
|
242
|
-
const agent = agentsService.updateAgent(
|
|
246
|
+
const agent = await agentsService.updateAgent(
|
|
247
|
+
req.params.id,
|
|
248
|
+
req.body || {},
|
|
249
|
+
);
|
|
243
250
|
return res.json({ ok: true, agent });
|
|
244
251
|
} catch (error) {
|
|
245
252
|
const status = String(error.message || "").includes("not found")
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
const { kFallbackOnboardingModels } = require("../constants");
|
|
1
|
+
const { kFallbackOnboardingModels, OPENCLAW_DIR } = require("../constants");
|
|
2
2
|
const { createModelCatalogCache } = require("../model-catalog-cache");
|
|
3
|
+
const { resolveThinkingOptionsForModel } = require("../openclaw-thinking");
|
|
4
|
+
const { readOpenclawConfig } = require("../openclaw-config");
|
|
3
5
|
const { getCommandOutputCandidates } = require("../utils/command-output");
|
|
4
6
|
|
|
5
7
|
const runModelsGitSync = async (shellCmd) => {
|
|
@@ -182,6 +184,38 @@ const registerModelRoutes = ({
|
|
|
182
184
|
return res.json(response);
|
|
183
185
|
});
|
|
184
186
|
|
|
187
|
+
app.get("/api/models/thinking-options", async (req, res) => {
|
|
188
|
+
const modelKey = String(req.query?.modelKey || "").trim();
|
|
189
|
+
if (!modelKey.includes("/")) {
|
|
190
|
+
return res
|
|
191
|
+
.status(400)
|
|
192
|
+
.json({ ok: false, error: "modelKey is required (provider/model)" });
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const cfg = readOpenclawConfig({ openclawDir: OPENCLAW_DIR });
|
|
196
|
+
const globalThinkingDefault =
|
|
197
|
+
typeof cfg.agents?.defaults?.thinkingDefault === "string"
|
|
198
|
+
? cfg.agents.defaults.thinkingDefault.trim()
|
|
199
|
+
: null;
|
|
200
|
+
const { levels, modelDefault } = await resolveThinkingOptionsForModel({
|
|
201
|
+
modelKey,
|
|
202
|
+
});
|
|
203
|
+
const inheritedDefault = globalThinkingDefault || modelDefault || "off";
|
|
204
|
+
return res.json({
|
|
205
|
+
ok: true,
|
|
206
|
+
modelKey,
|
|
207
|
+
levels,
|
|
208
|
+
modelDefault,
|
|
209
|
+
inheritedDefault,
|
|
210
|
+
});
|
|
211
|
+
} catch (err) {
|
|
212
|
+
return res.status(500).json({
|
|
213
|
+
ok: false,
|
|
214
|
+
error: err.message || "Failed to resolve thinking options",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
185
219
|
app.get("/api/models/status", async (req, res) => {
|
|
186
220
|
try {
|
|
187
221
|
const output = await shellCmd("openclaw models status --json", {
|
|
@@ -93,7 +93,7 @@ const registerOnboardingRoutes = ({
|
|
|
93
93
|
authProfiles,
|
|
94
94
|
ensureGatewayProxyConfig,
|
|
95
95
|
getBaseUrl,
|
|
96
|
-
|
|
96
|
+
runOnboardedBootSequence,
|
|
97
97
|
}) => {
|
|
98
98
|
// Keep mutating onboarding routes marker-gated so in-progress imports
|
|
99
99
|
// can promote files before the final completion marker is written.
|
|
@@ -114,7 +114,7 @@ const registerOnboardingRoutes = ({
|
|
|
114
114
|
authProfiles,
|
|
115
115
|
ensureGatewayProxyConfig,
|
|
116
116
|
getBaseUrl,
|
|
117
|
-
|
|
117
|
+
runOnboardedBootSequence,
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
const kEnvVarNamePattern = /^[A-Z_][A-Z0-9_]*$/;
|
|
@@ -30,7 +30,7 @@ const registerSystemRoutes = ({
|
|
|
30
30
|
let envRestartPending = false;
|
|
31
31
|
let openclawSecretRuntimePromise = null;
|
|
32
32
|
const kManagedChannelTokenPattern =
|
|
33
|
-
/^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN)(?:_[A-Z0-9_]+)?$/;
|
|
33
|
+
/^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN|WHATSAPP_OWNER_NUMBER)(?:_[A-Z0-9_]+)?$/;
|
|
34
34
|
const kEnvVarsReservedForUserInput = new Set([
|
|
35
35
|
"GITHUB_WORKSPACE_REPO",
|
|
36
36
|
"GOG_KEYRING_PASSWORD",
|
|
@@ -838,7 +838,7 @@ const registerSystemRoutes = ({
|
|
|
838
838
|
}
|
|
839
839
|
restartRequiredState.markRestartInProgress();
|
|
840
840
|
try {
|
|
841
|
-
restartGateway();
|
|
841
|
+
await restartGateway();
|
|
842
842
|
envRestartPending = false;
|
|
843
843
|
restartRequiredState.clearRequired();
|
|
844
844
|
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
|
};
|