@chrysb/alphaclaw 0.8.1-beta.7 → 0.8.1-beta.8
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/shell.css +24 -0
- package/lib/public/js/app.js +1 -0
- package/lib/public/js/components/envars.js +7 -5
- package/lib/public/js/components/global-restart-banner.js +22 -9
- package/lib/public/js/hooks/use-app-shell-controller.js +14 -0
- package/lib/public/js/lib/api.js +7 -0
- package/lib/server/agents/agents.js +70 -28
- package/lib/server/routes/system.js +44 -4
- package/package.json +1 -1
package/lib/public/css/shell.css
CHANGED
|
@@ -44,6 +44,26 @@
|
|
|
44
44
|
flex-shrink: 0;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
.global-restart-banner__actions {
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 8px;
|
|
51
|
+
flex-shrink: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.global-restart-banner__dismiss {
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
padding: 2px;
|
|
59
|
+
color: #fde68a;
|
|
60
|
+
opacity: 0.85;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.global-restart-banner__dismiss:hover {
|
|
64
|
+
opacity: 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
.app-content {
|
|
48
68
|
grid-column: 3;
|
|
49
69
|
grid-row: 2;
|
|
@@ -392,6 +412,10 @@
|
|
|
392
412
|
position: static;
|
|
393
413
|
transform: none;
|
|
394
414
|
}
|
|
415
|
+
.global-restart-banner__actions {
|
|
416
|
+
width: 100%;
|
|
417
|
+
justify-content: flex-end;
|
|
418
|
+
}
|
|
395
419
|
.app-content {
|
|
396
420
|
grid-column: 1;
|
|
397
421
|
grid-row: 2;
|
package/lib/public/js/app.js
CHANGED
|
@@ -169,6 +169,7 @@ const App = () => {
|
|
|
169
169
|
visible=${controllerState.isAnyRestartRequired}
|
|
170
170
|
restarting=${controllerState.restartingGateway}
|
|
171
171
|
onRestart=${controllerActions.handleGatewayRestart}
|
|
172
|
+
onDismiss=${controllerActions.dismissRestartBanner}
|
|
172
173
|
/>
|
|
173
174
|
<${AppSidebar}
|
|
174
175
|
mobileSidebarOpen=${shellState.mobileSidebarOpen}
|
|
@@ -333,7 +333,9 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
333
333
|
setVars(nextVars);
|
|
334
334
|
setPendingCustomKeys([]);
|
|
335
335
|
setReservedKeys(new Set(data.reservedKeys || []));
|
|
336
|
-
|
|
336
|
+
if (data.restartRequired) {
|
|
337
|
+
onRestartRequired(true);
|
|
338
|
+
}
|
|
337
339
|
},
|
|
338
340
|
[onRestartRequired],
|
|
339
341
|
);
|
|
@@ -389,11 +391,11 @@ export const Envars = ({ onRestartRequired = () => {} }) => {
|
|
|
389
391
|
: "Environment variables saved",
|
|
390
392
|
"success",
|
|
391
393
|
);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
394
|
+
// Force-refresh /api/env so stale cached payload cannot overwrite newly
|
|
395
|
+
// saved values with older state right after save.
|
|
396
|
+
const latestPayload = await refreshEnvPayload({ force: true });
|
|
397
|
+
applyEnvPayload(latestPayload);
|
|
395
398
|
setSecretMaskEpoch((prev) => prev + 1);
|
|
396
|
-
baselineSignatureRef.current = getVarsSignature(sortedVars);
|
|
397
399
|
setDirty(false);
|
|
398
400
|
} catch (err) {
|
|
399
401
|
showToast("Failed to save: " + err.message, "error");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
2
|
import htm from "https://esm.sh/htm";
|
|
3
3
|
import { UpdateActionButton } from "./update-action-button.js";
|
|
4
|
+
import { CloseIcon } from "./icons.js";
|
|
4
5
|
|
|
5
6
|
const html = htm.bind(h);
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ export const GlobalRestartBanner = ({
|
|
|
8
9
|
visible = false,
|
|
9
10
|
restarting = false,
|
|
10
11
|
onRestart,
|
|
12
|
+
onDismiss = () => {},
|
|
11
13
|
}) => {
|
|
12
14
|
if (!visible) return null;
|
|
13
15
|
return html`
|
|
@@ -16,15 +18,26 @@ export const GlobalRestartBanner = ({
|
|
|
16
18
|
<p class="global-restart-banner__text">
|
|
17
19
|
Gateway restart required to apply pending configuration changes.
|
|
18
20
|
</p>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
<div class="global-restart-banner__actions">
|
|
22
|
+
<${UpdateActionButton}
|
|
23
|
+
onClick=${onRestart}
|
|
24
|
+
disabled=${restarting}
|
|
25
|
+
loading=${restarting}
|
|
26
|
+
warning=${true}
|
|
27
|
+
idleLabel="Restart Gateway"
|
|
28
|
+
loadingLabel="Restarting..."
|
|
29
|
+
className="global-restart-banner__button"
|
|
30
|
+
/>
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
onclick=${onDismiss}
|
|
34
|
+
class="global-restart-banner__dismiss ac-btn-ghost"
|
|
35
|
+
aria-label="Dismiss restart banner"
|
|
36
|
+
title="Dismiss"
|
|
37
|
+
>
|
|
38
|
+
<${CloseIcon} className="h-3.5 w-3.5" />
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
28
41
|
</div>
|
|
29
42
|
</div>
|
|
30
43
|
`;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
fetchAlphaclawVersion,
|
|
7
7
|
updateAlphaclaw,
|
|
8
8
|
fetchRestartStatus,
|
|
9
|
+
dismissRestartStatus,
|
|
9
10
|
restartGateway,
|
|
10
11
|
fetchWatchdogStatus,
|
|
11
12
|
fetchDoctorStatus,
|
|
@@ -261,6 +262,18 @@ export const useAppShellController = ({ location = "" } = {}) => {
|
|
|
261
262
|
}
|
|
262
263
|
}, [acUpdating]);
|
|
263
264
|
|
|
265
|
+
const dismissRestartBanner = useCallback(async () => {
|
|
266
|
+
setRestartRequired(false);
|
|
267
|
+
setBrowseRestartRequired(false);
|
|
268
|
+
try {
|
|
269
|
+
await dismissRestartStatus();
|
|
270
|
+
await refreshRestartStatus();
|
|
271
|
+
} catch (err) {
|
|
272
|
+
showToast(err.message || "Could not dismiss restart banner", "error");
|
|
273
|
+
await refreshRestartStatus();
|
|
274
|
+
}
|
|
275
|
+
}, [refreshRestartStatus]);
|
|
276
|
+
|
|
264
277
|
return {
|
|
265
278
|
state: {
|
|
266
279
|
acHasUpdate,
|
|
@@ -284,6 +297,7 @@ export const useAppShellController = ({ location = "" } = {}) => {
|
|
|
284
297
|
handleOpenclawUpdate,
|
|
285
298
|
handleOpenclawVersionActionComplete,
|
|
286
299
|
refreshSharedStatuses,
|
|
300
|
+
dismissRestartBanner,
|
|
287
301
|
setRestartRequired,
|
|
288
302
|
},
|
|
289
303
|
};
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -346,6 +346,13 @@ export async function fetchRestartStatus() {
|
|
|
346
346
|
return parseJsonOrThrow(res, "Could not load restart status");
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
export async function dismissRestartStatus() {
|
|
350
|
+
const res = await authFetch("/api/restart-status/dismiss", {
|
|
351
|
+
method: "POST",
|
|
352
|
+
});
|
|
353
|
+
return parseJsonOrThrow(res, "Could not dismiss restart status");
|
|
354
|
+
}
|
|
355
|
+
|
|
349
356
|
export async function fetchWatchdogStatus() {
|
|
350
357
|
const res = await authFetch("/api/watchdog/status");
|
|
351
358
|
return parseJsonOrThrow(res, "Could not load watchdog status");
|
|
@@ -14,18 +14,40 @@ const {
|
|
|
14
14
|
ensureAgentScaffold,
|
|
15
15
|
} = require("./shared");
|
|
16
16
|
|
|
17
|
+
const toTitleWords = (value = "") =>
|
|
18
|
+
String(value || "")
|
|
19
|
+
.trim()
|
|
20
|
+
.split(/[-_\s]+/)
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
23
|
+
.join(" ");
|
|
24
|
+
|
|
25
|
+
const getFallbackAgentName = (agentId = "") => {
|
|
26
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
27
|
+
if (!normalizedAgentId) return "Agent";
|
|
28
|
+
const title = toTitleWords(normalizedAgentId) || normalizedAgentId;
|
|
29
|
+
return `${title} Agent`;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getAgentDisplayName = (agent = {}) =>
|
|
33
|
+
String(agent?.identity?.name || "").trim() ||
|
|
34
|
+
String(agent?.name || "").trim() ||
|
|
35
|
+
getFallbackAgentName(agent?.id || "");
|
|
36
|
+
|
|
37
|
+
const toReadableAgent = (agent = {}) => ({
|
|
38
|
+
...agent,
|
|
39
|
+
id: String(agent.id || "").trim(),
|
|
40
|
+
name: getAgentDisplayName(agent),
|
|
41
|
+
default: !!agent.default,
|
|
42
|
+
});
|
|
43
|
+
|
|
17
44
|
const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
18
45
|
const listAgents = () => {
|
|
19
46
|
const cfg = withNormalizedAgentsConfig({
|
|
20
47
|
OPENCLAW_DIR,
|
|
21
48
|
cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
|
|
22
49
|
});
|
|
23
|
-
return (cfg.agents?.list || []).map((entry) => (
|
|
24
|
-
...entry,
|
|
25
|
-
id: String(entry.id || "").trim(),
|
|
26
|
-
name: String(entry.name || "").trim() || String(entry.id || "").trim(),
|
|
27
|
-
default: !!entry.default,
|
|
28
|
-
}));
|
|
50
|
+
return (cfg.agents?.list || []).map((entry) => toReadableAgent(entry));
|
|
29
51
|
};
|
|
30
52
|
|
|
31
53
|
const getAgent = (agentId) => {
|
|
@@ -84,20 +106,29 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
84
106
|
OPENCLAW_DIR,
|
|
85
107
|
agentId,
|
|
86
108
|
});
|
|
109
|
+
const requestedIdentity =
|
|
110
|
+
input.identity && typeof input.identity === "object"
|
|
111
|
+
? { ...input.identity }
|
|
112
|
+
: {};
|
|
113
|
+
const requestedName = String(input.name || "").trim();
|
|
114
|
+
const identityName =
|
|
115
|
+
requestedName ||
|
|
116
|
+
String(requestedIdentity.name || "").trim() ||
|
|
117
|
+
getFallbackAgentName(agentId);
|
|
87
118
|
const nextAgent = {
|
|
88
119
|
id: agentId,
|
|
89
|
-
name: String(input.name || "").trim() || agentId,
|
|
90
120
|
default: false,
|
|
91
121
|
workspace: scaffoldWorkspacePath,
|
|
92
122
|
agentDir: agentDirPath,
|
|
123
|
+
identity: {
|
|
124
|
+
...requestedIdentity,
|
|
125
|
+
name: identityName,
|
|
126
|
+
},
|
|
93
127
|
...(input.model ? { model: input.model } : {}),
|
|
94
|
-
...(input.identity && typeof input.identity === "object"
|
|
95
|
-
? { identity: { ...input.identity } }
|
|
96
|
-
: {}),
|
|
97
128
|
};
|
|
98
129
|
cfg.agents.list = [...cfg.agents.list, nextAgent];
|
|
99
130
|
saveConfig({ fsImpl, OPENCLAW_DIR, config: cfg });
|
|
100
|
-
return nextAgent;
|
|
131
|
+
return toReadableAgent(nextAgent);
|
|
101
132
|
};
|
|
102
133
|
|
|
103
134
|
const updateAgent = (agentId, patch = {}) => {
|
|
@@ -109,20 +140,29 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
109
140
|
const index = cfg.agents.list.findIndex((entry) => entry.id === normalized);
|
|
110
141
|
if (index < 0) throw new Error(`Agent "${normalized}" not found`);
|
|
111
142
|
const current = cfg.agents.list[index];
|
|
112
|
-
const next = {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
identity
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
143
|
+
const next = { ...current };
|
|
144
|
+
const identityPatched =
|
|
145
|
+
patch.identity !== undefined || patch.name !== undefined;
|
|
146
|
+
if (identityPatched) {
|
|
147
|
+
const baseIdentity =
|
|
148
|
+
patch.identity !== undefined
|
|
149
|
+
? patch.identity && typeof patch.identity === "object"
|
|
150
|
+
? { ...patch.identity }
|
|
151
|
+
: {}
|
|
152
|
+
: current.identity && typeof current.identity === "object"
|
|
153
|
+
? { ...current.identity }
|
|
154
|
+
: {};
|
|
155
|
+
const requestedName =
|
|
156
|
+
patch.name !== undefined
|
|
157
|
+
? String(patch.name || "").trim()
|
|
158
|
+
: String(baseIdentity.name || "").trim();
|
|
159
|
+
const fallbackLegacyName = String(current.name || "").trim();
|
|
160
|
+
baseIdentity.name =
|
|
161
|
+
requestedName || fallbackLegacyName || getFallbackAgentName(normalized);
|
|
162
|
+
next.identity = baseIdentity;
|
|
163
|
+
// Only remove legacy top-level name once identity.name is persisted.
|
|
164
|
+
delete next.name;
|
|
165
|
+
}
|
|
126
166
|
if (patch.model !== undefined) {
|
|
127
167
|
if (patch.model === null) {
|
|
128
168
|
delete next.model;
|
|
@@ -134,7 +174,10 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
134
174
|
if (patch.tools && typeof patch.tools === "object") {
|
|
135
175
|
const toolsCfg = {};
|
|
136
176
|
if (patch.tools.profile) toolsCfg.profile = String(patch.tools.profile);
|
|
137
|
-
if (
|
|
177
|
+
if (
|
|
178
|
+
Array.isArray(patch.tools.alsoAllow) &&
|
|
179
|
+
patch.tools.alsoAllow.length
|
|
180
|
+
) {
|
|
138
181
|
toolsCfg.alsoAllow = patch.tools.alsoAllow.map(String);
|
|
139
182
|
}
|
|
140
183
|
if (Array.isArray(patch.tools.deny) && patch.tools.deny.length) {
|
|
@@ -145,10 +188,9 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
145
188
|
delete next.tools;
|
|
146
189
|
}
|
|
147
190
|
}
|
|
148
|
-
if (!String(next.name || "").trim()) next.name = normalized;
|
|
149
191
|
cfg.agents.list[index] = next;
|
|
150
192
|
saveConfig({ fsImpl, OPENCLAW_DIR, config: cfg });
|
|
151
|
-
return next;
|
|
193
|
+
return toReadableAgent(next);
|
|
152
194
|
};
|
|
153
195
|
|
|
154
196
|
const setDefaultAgent = (agentId) => {
|
|
@@ -151,16 +151,40 @@ const registerSystemRoutes = ({
|
|
|
151
151
|
}
|
|
152
152
|
return "Telegram";
|
|
153
153
|
};
|
|
154
|
-
const
|
|
154
|
+
const getDefaultAgentLabel = (config = {}) => {
|
|
155
|
+
return "Main Agent";
|
|
156
|
+
};
|
|
157
|
+
const getFallbackAgentLabel = (agentId = "") => {
|
|
158
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
159
|
+
if (!normalizedAgentId) return "Agent";
|
|
160
|
+
const titledAgentId = toTitleWords(normalizedAgentId) || normalizedAgentId;
|
|
161
|
+
return `${titledAgentId} Agent`;
|
|
162
|
+
};
|
|
163
|
+
const getConfiguredAgentLabel = (config = {}, agentId = "") => {
|
|
164
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
165
|
+
if (!normalizedAgentId) return "Agent";
|
|
166
|
+
const configuredAgents = Array.isArray(config?.agents?.list)
|
|
167
|
+
? config.agents.list
|
|
168
|
+
: [];
|
|
169
|
+
const configuredAgent = configuredAgents.find(
|
|
170
|
+
(entry) => String(entry?.id || "").trim() === normalizedAgentId,
|
|
171
|
+
);
|
|
172
|
+
const configuredName =
|
|
173
|
+
String(configuredAgent?.name || "").trim() ||
|
|
174
|
+
String(configuredAgent?.identity?.name || "").trim();
|
|
175
|
+
if (configuredName) return configuredName;
|
|
176
|
+
if (normalizedAgentId === "main") return getDefaultAgentLabel(config);
|
|
177
|
+
return getFallbackAgentLabel(normalizedAgentId);
|
|
178
|
+
};
|
|
179
|
+
const getAgentLabelFromSessionKey = (key = "", config = {}) => {
|
|
155
180
|
const match = String(key || "").match(/^agent:([^:]+):/);
|
|
156
181
|
const agentId = String(match?.[1] || "").trim();
|
|
157
182
|
if (!agentId) return "Agent";
|
|
158
|
-
|
|
159
|
-
return toTitleWords(agentId);
|
|
183
|
+
return getConfiguredAgentLabel(config, agentId);
|
|
160
184
|
};
|
|
161
185
|
const buildSessionLabel = (sessionRow = {}, config = {}) => {
|
|
162
186
|
const key = String(sessionRow?.key || "");
|
|
163
|
-
const agentLabel = getAgentLabelFromSessionKey(key);
|
|
187
|
+
const agentLabel = getAgentLabelFromSessionKey(key, config);
|
|
164
188
|
const agentKeyMatch = key.match(/^agent:([^:]+):/);
|
|
165
189
|
const agentId = String(agentKeyMatch?.[1] || "").trim();
|
|
166
190
|
const telegramChannelName = resolveTelegramChannelNameForAgent({
|
|
@@ -749,6 +773,22 @@ const registerSystemRoutes = ({
|
|
|
749
773
|
}
|
|
750
774
|
});
|
|
751
775
|
|
|
776
|
+
app.post("/api/restart-status/dismiss", async (req, res) => {
|
|
777
|
+
try {
|
|
778
|
+
envRestartPending = false;
|
|
779
|
+
restartRequiredState.clearRequired();
|
|
780
|
+
const snapshot = await restartRequiredState.getSnapshot();
|
|
781
|
+
res.json({
|
|
782
|
+
ok: true,
|
|
783
|
+
restartRequired: snapshot.restartRequired || envRestartPending,
|
|
784
|
+
restartInProgress: snapshot.restartInProgress,
|
|
785
|
+
gatewayRunning: snapshot.gatewayRunning,
|
|
786
|
+
});
|
|
787
|
+
} catch (err) {
|
|
788
|
+
res.status(500).json({ ok: false, error: err.message });
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
|
|
752
792
|
app.post("/api/gateway/restart", async (req, res) => {
|
|
753
793
|
if (!isOnboarded()) {
|
|
754
794
|
return res.status(400).json({ ok: false, error: "Not onboarded" });
|