@chrysb/alphaclaw 0.9.15 → 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 +2168 -2103
- 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/file-tree.js +82 -6
- package/lib/public/js/components/row-accessory-select.js +52 -0
- package/lib/public/js/lib/api.js +11 -1
- 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/browse/constants.js +3 -1
- package/lib/server/routes/browse/index.js +39 -11
- 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
|
@@ -3,34 +3,11 @@ const startServerLifecycle = ({
|
|
|
3
3
|
PORT,
|
|
4
4
|
isOnboarded,
|
|
5
5
|
runOnboardedBootSequence,
|
|
6
|
-
ensureManagedExecDefaults,
|
|
7
|
-
ensureUsageTrackerPluginConfig,
|
|
8
|
-
doSyncPromptFiles,
|
|
9
|
-
reloadEnv,
|
|
10
|
-
syncChannelConfig,
|
|
11
|
-
readEnvFile,
|
|
12
|
-
ensureGatewayProxyConfig,
|
|
13
|
-
resolveSetupUrl,
|
|
14
|
-
startGateway,
|
|
15
|
-
watchdog,
|
|
16
|
-
gmailWatchService,
|
|
17
6
|
}) => {
|
|
18
7
|
server.listen(PORT, "0.0.0.0", () => {
|
|
19
8
|
console.log(`[alphaclaw] Express listening on :${PORT}`);
|
|
20
9
|
if (isOnboarded()) {
|
|
21
|
-
runOnboardedBootSequence(
|
|
22
|
-
ensureManagedExecDefaults,
|
|
23
|
-
ensureUsageTrackerPluginConfig,
|
|
24
|
-
doSyncPromptFiles,
|
|
25
|
-
reloadEnv,
|
|
26
|
-
syncChannelConfig,
|
|
27
|
-
readEnvFile,
|
|
28
|
-
ensureGatewayProxyConfig,
|
|
29
|
-
resolveSetupUrl,
|
|
30
|
-
startGateway,
|
|
31
|
-
watchdog,
|
|
32
|
-
gmailWatchService,
|
|
33
|
-
});
|
|
10
|
+
runOnboardedBootSequence();
|
|
34
11
|
} else {
|
|
35
12
|
console.log("[alphaclaw] Awaiting onboarding via Setup UI");
|
|
36
13
|
}
|
|
@@ -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,4 +1,5 @@
|
|
|
1
|
-
const kDefaultTreeDepth =
|
|
1
|
+
const kDefaultTreeDepth = 3;
|
|
2
|
+
const kMaxTreeDepth = 3;
|
|
2
3
|
const kIgnoredDirectoryNames = new Set([
|
|
3
4
|
".git",
|
|
4
5
|
".alphaclaw",
|
|
@@ -42,6 +43,7 @@ const kSqliteTablePageSize = 50;
|
|
|
42
43
|
|
|
43
44
|
module.exports = {
|
|
44
45
|
kDefaultTreeDepth,
|
|
46
|
+
kMaxTreeDepth,
|
|
45
47
|
kIgnoredDirectoryNames,
|
|
46
48
|
kImageMimeTypeByExtension,
|
|
47
49
|
kCommitHistoryLimit,
|
|
@@ -2,6 +2,7 @@ const path = require("path");
|
|
|
2
2
|
const { kLockedBrowsePaths, kProtectedBrowsePaths } = require("../../constants");
|
|
3
3
|
const {
|
|
4
4
|
kDefaultTreeDepth,
|
|
5
|
+
kMaxTreeDepth,
|
|
5
6
|
kIgnoredDirectoryNames,
|
|
6
7
|
kCommitHistoryLimit,
|
|
7
8
|
} = require("./constants");
|
|
@@ -34,6 +35,16 @@ const registerBrowseRoutes = ({ app, fs, kRootDir }) => {
|
|
|
34
35
|
fs.mkdirSync(kRootResolved, { recursive: true });
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
const readVisibleDirectoryEntries = (absolutePath) =>
|
|
39
|
+
fs
|
|
40
|
+
.readdirSync(absolutePath, { withFileTypes: true })
|
|
41
|
+
.filter((entry) => {
|
|
42
|
+
if (entry.isDirectory() && kIgnoredDirectoryNames.has(entry.name)) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return entry.isDirectory() || entry.isFile();
|
|
46
|
+
});
|
|
47
|
+
|
|
37
48
|
const buildTreeNode = (absolutePath, depthRemaining) => {
|
|
38
49
|
const stats = fs.statSync(absolutePath);
|
|
39
50
|
const nodeName = path.basename(absolutePath);
|
|
@@ -44,17 +55,17 @@ const registerBrowseRoutes = ({ app, fs, kRootDir }) => {
|
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
if (depthRemaining <= 0) {
|
|
47
|
-
|
|
58
|
+
const hasMoreChildren = readVisibleDirectoryEntries(absolutePath).length > 0;
|
|
59
|
+
return {
|
|
60
|
+
type: "folder",
|
|
61
|
+
name: nodeName,
|
|
62
|
+
path: nodePath,
|
|
63
|
+
children: [],
|
|
64
|
+
truncated: hasMoreChildren,
|
|
65
|
+
};
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
const children =
|
|
51
|
-
.readdirSync(absolutePath, { withFileTypes: true })
|
|
52
|
-
.filter((entry) => {
|
|
53
|
-
if (entry.isDirectory() && kIgnoredDirectoryNames.has(entry.name)) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
return entry.isDirectory() || entry.isFile();
|
|
57
|
-
})
|
|
68
|
+
const children = readVisibleDirectoryEntries(absolutePath)
|
|
58
69
|
.map((entry) =>
|
|
59
70
|
buildTreeNode(path.join(absolutePath, entry.name), depthRemaining - 1),
|
|
60
71
|
)
|
|
@@ -70,12 +81,29 @@ const registerBrowseRoutes = ({ app, fs, kRootDir }) => {
|
|
|
70
81
|
|
|
71
82
|
app.get("/api/browse/tree", (req, res) => {
|
|
72
83
|
const depthValue = Number.parseInt(String(req.query.depth || ""), 10);
|
|
73
|
-
const
|
|
84
|
+
const requestedDepth =
|
|
74
85
|
Number.isFinite(depthValue) && depthValue > 0
|
|
75
86
|
? depthValue
|
|
76
87
|
: kDefaultTreeDepth;
|
|
88
|
+
const depth = Math.min(requestedDepth, kMaxTreeDepth);
|
|
89
|
+
const requestedPath = String(req.query.path || "").trim();
|
|
77
90
|
try {
|
|
78
|
-
const
|
|
91
|
+
const resolvedPath = requestedPath
|
|
92
|
+
? resolveSafePath(
|
|
93
|
+
requestedPath,
|
|
94
|
+
kRootResolved,
|
|
95
|
+
kRootWithSep,
|
|
96
|
+
kRootDisplayName,
|
|
97
|
+
)
|
|
98
|
+
: { ok: true, absolutePath: kRootResolved };
|
|
99
|
+
if (!resolvedPath.ok) {
|
|
100
|
+
return res.status(400).json({ ok: false, error: resolvedPath.error });
|
|
101
|
+
}
|
|
102
|
+
const stats = fs.statSync(resolvedPath.absolutePath);
|
|
103
|
+
if (!stats.isDirectory()) {
|
|
104
|
+
return res.status(400).json({ ok: false, error: "Path is not a folder" });
|
|
105
|
+
}
|
|
106
|
+
const tree = buildTreeNode(resolvedPath.absolutePath, depth);
|
|
79
107
|
return res.json({ ok: true, root: tree });
|
|
80
108
|
} catch (error) {
|
|
81
109
|
return res.status(500).json({
|
|
@@ -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", {
|