@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
|
@@ -3,6 +3,7 @@ import htm from "htm";
|
|
|
3
3
|
import { Badge } from "../../badge.js";
|
|
4
4
|
import { LoadingSpinner } from "../../loading-spinner.js";
|
|
5
5
|
import { OverflowMenu, OverflowMenuItem } from "../../overflow-menu.js";
|
|
6
|
+
import { RowAccessorySelect } from "../../row-accessory-select.js";
|
|
6
7
|
import {
|
|
7
8
|
getModelDisplayLabel,
|
|
8
9
|
SearchableModelPicker,
|
|
@@ -22,16 +23,25 @@ export const AgentModelCard = ({
|
|
|
22
23
|
canEditModel,
|
|
23
24
|
effectiveModel,
|
|
24
25
|
effectiveModelEntry,
|
|
26
|
+
formatInheritedThinkingLabel,
|
|
25
27
|
handleClearModelOverride,
|
|
26
28
|
handleSelectModel,
|
|
29
|
+
handleSelectThinkingDefault,
|
|
27
30
|
hasDistinctModelOverride,
|
|
31
|
+
hasDistinctThinkingOverride,
|
|
32
|
+
inheritedThinkingDefault,
|
|
28
33
|
loading,
|
|
29
34
|
menuOpen,
|
|
30
35
|
modelEntries,
|
|
31
36
|
popularModels,
|
|
32
37
|
remainingModelOptions,
|
|
33
38
|
setMenuOpen,
|
|
39
|
+
showThinkingSelect,
|
|
40
|
+
thinkingOptionsLoading,
|
|
41
|
+
thinkingSelectOptions,
|
|
42
|
+
thinkingSelectValue,
|
|
34
43
|
updatingModel,
|
|
44
|
+
updatingThinking,
|
|
35
45
|
} = useModelCard({
|
|
36
46
|
agent,
|
|
37
47
|
onUpdateAgent,
|
|
@@ -63,7 +73,19 @@ export const AgentModelCard = ({
|
|
|
63
73
|
handleClearModelOverride();
|
|
64
74
|
}}
|
|
65
75
|
>
|
|
66
|
-
Inherit from defaults
|
|
76
|
+
Inherit model from defaults
|
|
77
|
+
</${OverflowMenuItem}>
|
|
78
|
+
`
|
|
79
|
+
: null}
|
|
80
|
+
${hasDistinctThinkingOverride
|
|
81
|
+
? html`
|
|
82
|
+
<${OverflowMenuItem}
|
|
83
|
+
onClick=${() => {
|
|
84
|
+
setMenuOpen(false);
|
|
85
|
+
handleSelectThinkingDefault("");
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
Inherit thinking from defaults
|
|
67
89
|
</${OverflowMenuItem}>
|
|
68
90
|
`
|
|
69
91
|
: null}
|
|
@@ -93,17 +115,20 @@ export const AgentModelCard = ({
|
|
|
93
115
|
</p>`
|
|
94
116
|
: html`
|
|
95
117
|
<div class="space-y-1">
|
|
96
|
-
${modelEntries.map(
|
|
97
|
-
|
|
118
|
+
${modelEntries.map((entry) => {
|
|
119
|
+
const isPrimary = entry.key === effectiveModel;
|
|
120
|
+
const showThinkingPicker =
|
|
121
|
+
isPrimary && showThinkingSelect && !thinkingOptionsLoading;
|
|
122
|
+
return html`
|
|
98
123
|
<div
|
|
99
124
|
key=${entry.key}
|
|
100
|
-
class="flex items-center justify-between py-1"
|
|
125
|
+
class="flex items-center justify-between gap-3 py-1"
|
|
101
126
|
>
|
|
102
127
|
<div class="flex items-center gap-2 min-w-0">
|
|
103
128
|
<span class="text-sm text-body truncate">
|
|
104
129
|
${getModelDisplayLabel(entry)}
|
|
105
130
|
</span>
|
|
106
|
-
${
|
|
131
|
+
${isPrimary
|
|
107
132
|
? html`<${Badge} tone="cyan">Primary</${Badge}>`
|
|
108
133
|
: html`
|
|
109
134
|
<button
|
|
@@ -115,9 +140,36 @@ export const AgentModelCard = ({
|
|
|
115
140
|
</button>
|
|
116
141
|
`}
|
|
117
142
|
</div>
|
|
143
|
+
${showThinkingPicker
|
|
144
|
+
? html`
|
|
145
|
+
<${RowAccessorySelect}
|
|
146
|
+
ariaLabel="Agent thinking level"
|
|
147
|
+
title="Agent thinking level"
|
|
148
|
+
value=${thinkingSelectValue}
|
|
149
|
+
disabled=${saving ||
|
|
150
|
+
updatingModel ||
|
|
151
|
+
updatingThinking ||
|
|
152
|
+
!canEditModel}
|
|
153
|
+
onChange=${handleSelectThinkingDefault}
|
|
154
|
+
>
|
|
155
|
+
<option value="">
|
|
156
|
+
${formatInheritedThinkingLabel(
|
|
157
|
+
inheritedThinkingDefault,
|
|
158
|
+
)}
|
|
159
|
+
</option>
|
|
160
|
+
${thinkingSelectOptions.map(
|
|
161
|
+
(option) => html`
|
|
162
|
+
<option value=${option.value}>
|
|
163
|
+
${option.label}
|
|
164
|
+
</option>
|
|
165
|
+
`,
|
|
166
|
+
)}
|
|
167
|
+
</${RowAccessorySelect}>
|
|
168
|
+
`
|
|
169
|
+
: null}
|
|
118
170
|
</div>
|
|
119
|
-
|
|
120
|
-
)}
|
|
171
|
+
`;
|
|
172
|
+
})}
|
|
121
173
|
</div>
|
|
122
174
|
`}
|
|
123
175
|
${loading
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "preact/hooks";
|
|
2
|
+
import { fetchThinkingOptions } from "../../../lib/api.js";
|
|
3
|
+
import {
|
|
4
|
+
formatInheritedThinkingLabel,
|
|
5
|
+
formatThinkingLevelLabel,
|
|
6
|
+
shouldShowThinkingLevelSelect,
|
|
7
|
+
} from "../../../lib/thinking-levels.js";
|
|
2
8
|
import { useModels } from "../../models-tab/use-models.js";
|
|
3
9
|
import {
|
|
4
10
|
buildProviderHasAuth,
|
|
@@ -25,7 +31,14 @@ export const useModelCard = ({
|
|
|
25
31
|
onUpdateAgent = async () => {},
|
|
26
32
|
}) => {
|
|
27
33
|
const [updatingModel, setUpdatingModel] = useState(false);
|
|
34
|
+
const [updatingThinking, setUpdatingThinking] = useState(false);
|
|
28
35
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
36
|
+
const [thinkingOptions, setThinkingOptions] = useState({
|
|
37
|
+
levels: [],
|
|
38
|
+
inheritedDefault: "off",
|
|
39
|
+
modelDefault: "off",
|
|
40
|
+
});
|
|
41
|
+
const [thinkingOptionsLoading, setThinkingOptionsLoading] = useState(false);
|
|
29
42
|
const {
|
|
30
43
|
catalog,
|
|
31
44
|
primary: defaultPrimaryModel,
|
|
@@ -41,6 +54,45 @@ export const useModelCard = ({
|
|
|
41
54
|
const hasDistinctModelOverride =
|
|
42
55
|
!!explicitModel &&
|
|
43
56
|
String(explicitModel).trim() !== String(defaultPrimaryModel || "").trim();
|
|
57
|
+
const explicitThinkingDefault = String(agent.thinkingDefault || "").trim();
|
|
58
|
+
const inheritedThinkingDefault = String(
|
|
59
|
+
thinkingOptions.inheritedDefault || thinkingOptions.modelDefault || "off",
|
|
60
|
+
).trim();
|
|
61
|
+
const hasDistinctThinkingOverride =
|
|
62
|
+
!!explicitThinkingDefault &&
|
|
63
|
+
explicitThinkingDefault !== inheritedThinkingDefault;
|
|
64
|
+
const showThinkingSelect = shouldShowThinkingLevelSelect(
|
|
65
|
+
thinkingOptions.levels,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const modelKey = String(effectiveModel || "").trim();
|
|
70
|
+
if (!modelKey.includes("/")) {
|
|
71
|
+
setThinkingOptions({
|
|
72
|
+
levels: [],
|
|
73
|
+
inheritedDefault: "off",
|
|
74
|
+
modelDefault: "off",
|
|
75
|
+
});
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
let cancelled = false;
|
|
79
|
+
setThinkingOptionsLoading(true);
|
|
80
|
+
fetchThinkingOptions(modelKey)
|
|
81
|
+
.then((payload) => {
|
|
82
|
+
if (cancelled || !payload?.ok) return;
|
|
83
|
+
setThinkingOptions({
|
|
84
|
+
levels: Array.isArray(payload.levels) ? payload.levels : [],
|
|
85
|
+
inheritedDefault: String(payload.inheritedDefault || "off").trim(),
|
|
86
|
+
modelDefault: String(payload.modelDefault || "off").trim(),
|
|
87
|
+
});
|
|
88
|
+
})
|
|
89
|
+
.finally(() => {
|
|
90
|
+
if (!cancelled) setThinkingOptionsLoading(false);
|
|
91
|
+
});
|
|
92
|
+
return () => {
|
|
93
|
+
cancelled = true;
|
|
94
|
+
};
|
|
95
|
+
}, [effectiveModel]);
|
|
44
96
|
|
|
45
97
|
const providerHasAuth = useMemo(
|
|
46
98
|
() => buildProviderHasAuth({ authProfiles, codexStatus }),
|
|
@@ -149,6 +201,69 @@ export const useModelCard = ({
|
|
|
149
201
|
}
|
|
150
202
|
};
|
|
151
203
|
|
|
204
|
+
const handleSelectThinkingDefault = async (nextValue) => {
|
|
205
|
+
const normalizedValue = String(nextValue || "").trim();
|
|
206
|
+
const isInherit = !normalizedValue;
|
|
207
|
+
if (isInherit) {
|
|
208
|
+
if (!hasDistinctThinkingOverride) return;
|
|
209
|
+
setUpdatingThinking(true);
|
|
210
|
+
try {
|
|
211
|
+
await onUpdateAgent(
|
|
212
|
+
String(agent.id || "").trim(),
|
|
213
|
+
{ thinkingDefault: null },
|
|
214
|
+
"Agent thinking level reset to default",
|
|
215
|
+
);
|
|
216
|
+
} finally {
|
|
217
|
+
setUpdatingThinking(false);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (normalizedValue === explicitThinkingDefault) return;
|
|
222
|
+
setUpdatingThinking(true);
|
|
223
|
+
try {
|
|
224
|
+
await onUpdateAgent(
|
|
225
|
+
String(agent.id || "").trim(),
|
|
226
|
+
{ thinkingDefault: normalizedValue },
|
|
227
|
+
"Agent thinking level updated",
|
|
228
|
+
);
|
|
229
|
+
} finally {
|
|
230
|
+
setUpdatingThinking(false);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const thinkingSelectValue = hasDistinctThinkingOverride
|
|
235
|
+
? explicitThinkingDefault
|
|
236
|
+
: "";
|
|
237
|
+
const thinkingSelectOptions = useMemo(() => {
|
|
238
|
+
const seen = new Set();
|
|
239
|
+
const options = [];
|
|
240
|
+
const addOption = (value, label) => {
|
|
241
|
+
const normalizedValue = String(value || "").trim();
|
|
242
|
+
if (!normalizedValue || seen.has(normalizedValue)) return;
|
|
243
|
+
seen.add(normalizedValue);
|
|
244
|
+
options.push({
|
|
245
|
+
value: normalizedValue,
|
|
246
|
+
label: String(label || formatThinkingLevelLabel(normalizedValue)).trim(),
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
for (const entry of thinkingOptions.levels) {
|
|
250
|
+
addOption(
|
|
251
|
+
entry?.id,
|
|
252
|
+
formatThinkingLevelLabel(entry?.label || entry?.id),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (
|
|
256
|
+
explicitThinkingDefault &&
|
|
257
|
+
!seen.has(explicitThinkingDefault)
|
|
258
|
+
) {
|
|
259
|
+
addOption(
|
|
260
|
+
explicitThinkingDefault,
|
|
261
|
+
`${formatThinkingLevelLabel(explicitThinkingDefault)} (custom)`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return options;
|
|
265
|
+
}, [explicitThinkingDefault, thinkingOptions.levels]);
|
|
266
|
+
|
|
152
267
|
return {
|
|
153
268
|
authorizedModelOptions,
|
|
154
269
|
canEditModel: modelsReady && !loadingModels,
|
|
@@ -156,13 +271,22 @@ export const useModelCard = ({
|
|
|
156
271
|
effectiveModelEntry,
|
|
157
272
|
handleClearModelOverride,
|
|
158
273
|
handleSelectModel,
|
|
274
|
+
handleSelectThinkingDefault,
|
|
159
275
|
hasDistinctModelOverride,
|
|
276
|
+
hasDistinctThinkingOverride,
|
|
277
|
+
inheritedThinkingDefault,
|
|
160
278
|
loading: !modelsReady || loadingModels,
|
|
161
279
|
menuOpen,
|
|
162
280
|
modelEntries,
|
|
163
281
|
popularModels,
|
|
164
282
|
remainingModelOptions,
|
|
165
283
|
setMenuOpen,
|
|
284
|
+
showThinkingSelect,
|
|
285
|
+
thinkingOptionsLoading,
|
|
286
|
+
thinkingSelectOptions,
|
|
287
|
+
thinkingSelectValue,
|
|
288
|
+
formatInheritedThinkingLabel,
|
|
166
289
|
updatingModel,
|
|
290
|
+
updatingThinking,
|
|
167
291
|
};
|
|
168
292
|
};
|
|
@@ -58,7 +58,7 @@ const normalizeEnvVarKey = (raw) =>
|
|
|
58
58
|
.toUpperCase()
|
|
59
59
|
.replace(/[^A-Z0-9_]/g, "_");
|
|
60
60
|
const kManagedChannelTokenPattern =
|
|
61
|
-
/^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN)(?:_[A-Z0-9_]+)?$/;
|
|
61
|
+
/^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN|WHATSAPP_OWNER_NUMBER)(?:_[A-Z0-9_]+)?$/;
|
|
62
62
|
const stripSurroundingQuotes = (raw) => {
|
|
63
63
|
const value = String(raw || "").trim();
|
|
64
64
|
if (value.length < 2) return value;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
import htm from "htm";
|
|
3
|
+
|
|
4
|
+
const html = htm.bind(h);
|
|
5
|
+
|
|
6
|
+
const RowAccessoryChevron = () => html`
|
|
7
|
+
<svg
|
|
8
|
+
width="14"
|
|
9
|
+
height="14"
|
|
10
|
+
viewBox="0 0 16 16"
|
|
11
|
+
fill="none"
|
|
12
|
+
class="text-fg-dim"
|
|
13
|
+
aria-hidden="true"
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
d="M3.5 6L8 10.5L12.5 6"
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
stroke-width="2"
|
|
19
|
+
stroke-linecap="round"
|
|
20
|
+
stroke-linejoin="round"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
export const RowAccessorySelect = ({
|
|
26
|
+
ariaLabel = "",
|
|
27
|
+
title = "",
|
|
28
|
+
value = "",
|
|
29
|
+
disabled = false,
|
|
30
|
+
onChange = () => {},
|
|
31
|
+
children = null,
|
|
32
|
+
}) => html`
|
|
33
|
+
<label
|
|
34
|
+
class=${`relative inline-flex shrink-0 items-center justify-end max-w-[12rem] min-w-[5.5rem] ${disabled
|
|
35
|
+
? "opacity-50 cursor-not-allowed"
|
|
36
|
+
: "cursor-pointer"}`}
|
|
37
|
+
>
|
|
38
|
+
<select
|
|
39
|
+
aria-label=${ariaLabel}
|
|
40
|
+
title=${title || ariaLabel}
|
|
41
|
+
value=${value}
|
|
42
|
+
disabled=${disabled}
|
|
43
|
+
onInput=${(event) => onChange(String(event.currentTarget?.value ?? ""))}
|
|
44
|
+
class="appearance-none bg-transparent border-0 py-0 pl-0 pr-5 w-full text-right text-xs text-fg-muted hover:text-body cursor-pointer focus:outline-none focus-visible:ring-1 focus-visible:ring-border rounded disabled:cursor-not-allowed truncate"
|
|
45
|
+
>
|
|
46
|
+
${children}
|
|
47
|
+
</select>
|
|
48
|
+
<span class="pointer-events-none absolute right-0 top-1/2 -translate-y-1/2">
|
|
49
|
+
<${RowAccessoryChevron} />
|
|
50
|
+
</span>
|
|
51
|
+
</label>
|
|
52
|
+
`;
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -857,6 +857,13 @@ export const fetchModelStatus = async () => {
|
|
|
857
857
|
return res.json();
|
|
858
858
|
};
|
|
859
859
|
|
|
860
|
+
export const fetchThinkingOptions = async (modelKey) => {
|
|
861
|
+
const normalized = String(modelKey || "").trim();
|
|
862
|
+
const qs = new URLSearchParams({ modelKey: normalized });
|
|
863
|
+
const res = await authFetch(`/api/models/thinking-options?${qs.toString()}`);
|
|
864
|
+
return res.json();
|
|
865
|
+
};
|
|
866
|
+
|
|
860
867
|
export const setPrimaryModel = async (modelKey) => {
|
|
861
868
|
const res = await authFetch("/api/models/set", {
|
|
862
869
|
method: "POST",
|
|
@@ -4,6 +4,7 @@ import { getFeaturedModels } from "./model-config.js";
|
|
|
4
4
|
|
|
5
5
|
export const kModelCatalogCacheKey = "/api/models";
|
|
6
6
|
export const kModelCatalogPollIntervalMs = 3000;
|
|
7
|
+
export const kDefaultOnboardingModelKey = "anthropic/claude-opus-4-8";
|
|
7
8
|
|
|
8
9
|
export const getModelCatalogModels = (payload) =>
|
|
9
10
|
Array.isArray(payload?.models) ? payload.models : [];
|
|
@@ -26,6 +27,11 @@ export const getInitialOnboardingModelKey = ({
|
|
|
26
27
|
} = {}) => {
|
|
27
28
|
const normalizedCurrent = String(currentModelKey || "").trim();
|
|
28
29
|
if (normalizedCurrent) return normalizedCurrent;
|
|
30
|
+
const catalogHasKey = (key) =>
|
|
31
|
+
catalog.some((model) => String(model?.key || "") === key);
|
|
32
|
+
if (catalogHasKey(kDefaultOnboardingModelKey)) {
|
|
33
|
+
return kDefaultOnboardingModelKey;
|
|
34
|
+
}
|
|
29
35
|
const featuredModels = getFeaturedModels(catalog);
|
|
30
36
|
return String(featuredModels[0]?.key || catalog[0]?.key || "");
|
|
31
37
|
};
|
|
@@ -9,6 +9,10 @@ export const getAuthProviderFromModelProvider = (provider) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const kFeaturedModelDefs = [
|
|
12
|
+
{
|
|
13
|
+
label: "Opus 4.8",
|
|
14
|
+
preferredKeys: ["anthropic/claude-opus-4-8"],
|
|
15
|
+
},
|
|
12
16
|
{
|
|
13
17
|
label: "Opus 4.7",
|
|
14
18
|
preferredKeys: ["anthropic/claude-opus-4-7"],
|
|
@@ -58,13 +62,14 @@ export const kProviderAuthFields = {
|
|
|
58
62
|
linkText: "Get key",
|
|
59
63
|
placeholder: "sk-ant-...",
|
|
60
64
|
},
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
// Temporarily hidden — setup-token flow is not supported in onboarding yet.
|
|
66
|
+
// {
|
|
67
|
+
// key: "ANTHROPIC_TOKEN",
|
|
68
|
+
// label: "Anthropic Setup Token",
|
|
69
|
+
// hint: "From claude setup-token (uses your Claude subscription)",
|
|
70
|
+
// linkText: "Get token",
|
|
71
|
+
// placeholder: "Token...",
|
|
72
|
+
// },
|
|
68
73
|
],
|
|
69
74
|
openai: [
|
|
70
75
|
{
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const kThinkingLevelLabelOverrides = {
|
|
2
|
+
off: "Off",
|
|
3
|
+
on: "On",
|
|
4
|
+
minimal: "Minimal",
|
|
5
|
+
low: "Low",
|
|
6
|
+
medium: "Medium",
|
|
7
|
+
high: "High",
|
|
8
|
+
adaptive: "Adaptive",
|
|
9
|
+
xhigh: "Extra high",
|
|
10
|
+
max: "Maximum",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const formatThinkingLevelLabel = (levelId = "") => {
|
|
14
|
+
const normalized = String(levelId || "").trim().toLowerCase();
|
|
15
|
+
if (!normalized) return "";
|
|
16
|
+
if (kThinkingLevelLabelOverrides[normalized]) {
|
|
17
|
+
return kThinkingLevelLabelOverrides[normalized];
|
|
18
|
+
}
|
|
19
|
+
return normalized
|
|
20
|
+
.split(/[-_]/g)
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
23
|
+
.join(" ");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const formatInheritedThinkingLabel = (levelId = "") => {
|
|
27
|
+
const label = formatThinkingLevelLabel(levelId);
|
|
28
|
+
return label ? `Inherited: ${label}` : "Inherited";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const shouldShowThinkingLevelSelect = (levels = []) => {
|
|
32
|
+
const normalized = (Array.isArray(levels) ? levels : [])
|
|
33
|
+
.map((entry) => String(entry?.id || entry || "").trim().toLowerCase())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
if (normalized.length === 0) return false;
|
|
36
|
+
return !(normalized.length === 1 && normalized[0] === "off");
|
|
37
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
+
const { normalizeThinkingDefaultValue } = require("../openclaw-thinking");
|
|
2
3
|
|
|
3
4
|
const {
|
|
4
5
|
kDefaultAgentId,
|
|
@@ -42,11 +43,25 @@ const toReadableAgent = (agent = {}) => ({
|
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
46
|
+
const readAgentsConfig = () =>
|
|
47
|
+
withNormalizedAgentsConfig({
|
|
47
48
|
OPENCLAW_DIR,
|
|
48
49
|
cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
|
|
49
50
|
});
|
|
51
|
+
|
|
52
|
+
const getAgentDefaults = () => {
|
|
53
|
+
const cfg = readAgentsConfig();
|
|
54
|
+
const thinkingDefault = cfg.agents?.defaults?.thinkingDefault;
|
|
55
|
+
return {
|
|
56
|
+
thinkingDefault:
|
|
57
|
+
typeof thinkingDefault === "string" && thinkingDefault.trim()
|
|
58
|
+
? thinkingDefault.trim()
|
|
59
|
+
: null,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const listAgents = () => {
|
|
64
|
+
const cfg = readAgentsConfig();
|
|
50
65
|
return (cfg.agents?.list || []).map((entry) => toReadableAgent(entry));
|
|
51
66
|
};
|
|
52
67
|
|
|
@@ -131,12 +146,9 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
131
146
|
return toReadableAgent(nextAgent);
|
|
132
147
|
};
|
|
133
148
|
|
|
134
|
-
const updateAgent = (agentId, patch = {}) => {
|
|
149
|
+
const updateAgent = async (agentId, patch = {}) => {
|
|
135
150
|
const normalized = String(agentId || "").trim();
|
|
136
|
-
const cfg =
|
|
137
|
-
OPENCLAW_DIR,
|
|
138
|
-
cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
|
|
139
|
-
});
|
|
151
|
+
const cfg = readAgentsConfig();
|
|
140
152
|
const index = cfg.agents.list.findIndex((entry) => entry.id === normalized);
|
|
141
153
|
if (index < 0) throw new Error(`Agent "${normalized}" not found`);
|
|
142
154
|
const current = cfg.agents.list[index];
|
|
@@ -188,6 +200,19 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
188
200
|
delete next.tools;
|
|
189
201
|
}
|
|
190
202
|
}
|
|
203
|
+
if (patch.thinkingDefault !== undefined) {
|
|
204
|
+
if (patch.thinkingDefault === null) {
|
|
205
|
+
delete next.thinkingDefault;
|
|
206
|
+
} else {
|
|
207
|
+
const normalizedThinking = await normalizeThinkingDefaultValue(
|
|
208
|
+
patch.thinkingDefault,
|
|
209
|
+
);
|
|
210
|
+
if (!normalizedThinking) {
|
|
211
|
+
throw new Error("Invalid thinkingDefault value");
|
|
212
|
+
}
|
|
213
|
+
next.thinkingDefault = normalizedThinking;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
191
216
|
cfg.agents.list[index] = next;
|
|
192
217
|
saveConfig({ fsImpl, OPENCLAW_DIR, config: cfg });
|
|
193
218
|
return toReadableAgent(next);
|
|
@@ -253,6 +278,7 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
253
278
|
return {
|
|
254
279
|
listAgents,
|
|
255
280
|
getAgent,
|
|
281
|
+
getAgentDefaults,
|
|
256
282
|
getAgentWorkspaceSize,
|
|
257
283
|
createAgent,
|
|
258
284
|
updateAgent,
|
|
@@ -270,7 +270,7 @@ const createChannelsDomain = ({
|
|
|
270
270
|
|
|
271
271
|
const previousConfig = cloneJson(cfg);
|
|
272
272
|
try {
|
|
273
|
-
onProgress({ phase: "
|
|
273
|
+
onProgress({ phase: "configuring", label: "Configuring..." });
|
|
274
274
|
writeEnvFile(nextEnvVars);
|
|
275
275
|
reloadEnv();
|
|
276
276
|
assertActiveChannelTokenEnvVars({
|
|
@@ -280,7 +280,6 @@ const createChannelsDomain = ({
|
|
|
280
280
|
}),
|
|
281
281
|
envVars: nextEnvVars,
|
|
282
282
|
});
|
|
283
|
-
await restartGateway();
|
|
284
283
|
const pluginEnabledCfg = withNormalizedAgentsConfig({
|
|
285
284
|
OPENCLAW_DIR,
|
|
286
285
|
cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
|
|
@@ -374,6 +373,8 @@ const createChannelsDomain = ({
|
|
|
374
373
|
"Could not bind channel account",
|
|
375
374
|
);
|
|
376
375
|
}
|
|
376
|
+
onProgress({ phase: "restarting", label: "Rebooting..." });
|
|
377
|
+
await restartGateway();
|
|
377
378
|
} catch (error) {
|
|
378
379
|
try {
|
|
379
380
|
await clawCmd(
|
|
@@ -760,6 +761,7 @@ const createChannelsDomain = ({
|
|
|
760
761
|
}
|
|
761
762
|
|
|
762
763
|
cleanupChannelAccountPairingFiles({ provider, accountId });
|
|
764
|
+
await restartGateway();
|
|
763
765
|
return { ok: true };
|
|
764
766
|
}
|
|
765
767
|
|
package/lib/server/chat-ws.js
CHANGED
|
@@ -6,7 +6,7 @@ const kEnvRefPattern = /^\$\{([A-Z0-9_]+)\}$/i;
|
|
|
6
6
|
const kConnectTimeoutMs = 8000;
|
|
7
7
|
const kHistoryTimeoutMs = 12000;
|
|
8
8
|
const kGatewayReqTimeoutMs = 15000;
|
|
9
|
-
const kGatewayProtocolVersion =
|
|
9
|
+
const kGatewayProtocolVersion = 4;
|
|
10
10
|
// Gateway method auth (see OpenClaw method-scopes): chat.history needs operator.read;
|
|
11
11
|
// chat.send / chat.abort need operator.write. Align with CLI_DEFAULT_OPERATOR_SCOPES plus admin.
|
|
12
12
|
const kGatewayChatBridgeScopes = [
|
|
@@ -283,6 +283,9 @@ const sanitizeError = (error) => {
|
|
|
283
283
|
) {
|
|
284
284
|
return "Gateway authentication failed. Verify OPENCLAW_GATEWAY_TOKEN matches the gateway.";
|
|
285
285
|
}
|
|
286
|
+
if (lower.includes("protocol mismatch")) {
|
|
287
|
+
return "Chat cannot connect to the gateway (protocol version mismatch). Update AlphaClaw to match your OpenClaw version.";
|
|
288
|
+
}
|
|
286
289
|
if (lower.includes("method not found") || lower.includes("unknown method")) {
|
|
287
290
|
return "This gateway build does not support chat APIs. Update OpenClaw.";
|
|
288
291
|
}
|
package/lib/server/constants.js
CHANGED
|
@@ -54,6 +54,22 @@ const kLoginMaxLockMs = parsePositiveInt(
|
|
|
54
54
|
process.env.LOGIN_RATE_MAX_LOCK_MS,
|
|
55
55
|
15 * 60 * 1000,
|
|
56
56
|
);
|
|
57
|
+
const kLoginGlobalWindowMs = parsePositiveInt(
|
|
58
|
+
process.env.LOGIN_RATE_GLOBAL_WINDOW_MS,
|
|
59
|
+
kLoginWindowMs,
|
|
60
|
+
);
|
|
61
|
+
const kLoginGlobalMaxAttempts = parsePositiveInt(
|
|
62
|
+
process.env.LOGIN_RATE_GLOBAL_MAX_ATTEMPTS,
|
|
63
|
+
Math.max(kLoginMaxAttempts * 5, 25),
|
|
64
|
+
);
|
|
65
|
+
const kLoginGlobalBaseLockMs = parsePositiveInt(
|
|
66
|
+
process.env.LOGIN_RATE_GLOBAL_BASE_LOCK_MS,
|
|
67
|
+
kLoginBaseLockMs,
|
|
68
|
+
);
|
|
69
|
+
const kLoginGlobalMaxLockMs = parsePositiveInt(
|
|
70
|
+
process.env.LOGIN_RATE_GLOBAL_MAX_LOCK_MS,
|
|
71
|
+
kLoginMaxLockMs,
|
|
72
|
+
);
|
|
57
73
|
const kLoginCleanupIntervalMs = parsePositiveInt(
|
|
58
74
|
process.env.LOGIN_RATE_CLEANUP_INTERVAL_MS,
|
|
59
75
|
60 * 1000,
|
|
@@ -92,6 +108,11 @@ const kOnboardingModelProviders = new Set([
|
|
|
92
108
|
"vllm",
|
|
93
109
|
]);
|
|
94
110
|
const kMinimalFallbackOnboardingModels = [
|
|
111
|
+
{
|
|
112
|
+
key: "anthropic/claude-opus-4-8",
|
|
113
|
+
provider: "anthropic",
|
|
114
|
+
label: "Claude Opus 4.8",
|
|
115
|
+
},
|
|
95
116
|
{
|
|
96
117
|
key: "anthropic/claude-opus-4-7",
|
|
97
118
|
provider: "anthropic",
|
|
@@ -445,6 +466,10 @@ module.exports = {
|
|
|
445
466
|
kLoginMaxAttempts,
|
|
446
467
|
kLoginBaseLockMs,
|
|
447
468
|
kLoginMaxLockMs,
|
|
469
|
+
kLoginGlobalWindowMs,
|
|
470
|
+
kLoginGlobalMaxAttempts,
|
|
471
|
+
kLoginGlobalBaseLockMs,
|
|
472
|
+
kLoginGlobalMaxLockMs,
|
|
448
473
|
kLoginCleanupIntervalMs,
|
|
449
474
|
kLoginStateTtlMs,
|
|
450
475
|
kOnboardingModelProviders,
|
package/lib/server/cost-utils.js
CHANGED
|
@@ -13,6 +13,8 @@ const kClaudeOpus47Pricing = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const kGlobalModelPricing = {
|
|
16
|
+
"claude-opus-4-8": kClaudeOpus47Pricing,
|
|
17
|
+
"claude-opus-4.8": kClaudeOpus47Pricing,
|
|
16
18
|
"claude-opus-4-7": kClaudeOpus47Pricing,
|
|
17
19
|
"claude-opus-4.7": kClaudeOpus47Pricing,
|
|
18
20
|
"claude-opus-4-6": {
|