@chrysb/alphaclaw 0.5.5 → 0.5.7-beta.0
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/bin/alphaclaw.js +6 -1
- package/lib/public/css/agents.css +92 -0
- package/lib/public/css/explorer.css +101 -0
- package/lib/public/css/shell.css +15 -4
- package/lib/public/js/app.js +69 -3
- package/lib/public/js/components/action-button.js +5 -0
- package/lib/public/js/components/agents-tab/agent-bindings-section/helpers.js +76 -0
- package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +490 -0
- package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +256 -0
- package/lib/public/js/components/agents-tab/agent-detail-panel.js +74 -0
- package/lib/public/js/components/agents-tab/agent-identity-section.js +175 -0
- package/lib/public/js/components/agents-tab/agent-overview/index.js +53 -0
- package/lib/public/js/components/agents-tab/agent-overview/manage-card.js +44 -0
- package/lib/public/js/components/agents-tab/agent-overview/model-card.js +158 -0
- package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +169 -0
- package/lib/public/js/components/agents-tab/agent-overview/use-workspace-card.js +45 -0
- package/lib/public/js/components/agents-tab/agent-overview/workspace-card.js +47 -0
- package/lib/public/js/components/agents-tab/agent-pairing-section.js +265 -0
- package/lib/public/js/components/agents-tab/create-agent-modal.js +189 -0
- package/lib/public/js/components/agents-tab/create-channel-modal.js +323 -0
- package/lib/public/js/components/agents-tab/delete-agent-dialog.js +50 -0
- package/lib/public/js/components/agents-tab/edit-agent-modal.js +109 -0
- package/lib/public/js/components/agents-tab/index.js +148 -0
- package/lib/public/js/components/agents-tab/use-agents.js +89 -0
- package/lib/public/js/components/channel-account-status-badge.js +35 -0
- package/lib/public/js/components/channel-operations-panel.js +33 -0
- package/lib/public/js/components/channels.js +545 -60
- package/lib/public/js/components/envars.js +25 -4
- package/lib/public/js/components/general/index.js +21 -11
- package/lib/public/js/components/general/use-general-tab.js +78 -16
- package/lib/public/js/components/google/gmail-setup-wizard.js +1 -3
- package/lib/public/js/components/google/index.js +28 -30
- package/lib/public/js/components/icons.js +37 -0
- package/lib/public/js/components/models-tab/index.js +58 -224
- package/lib/public/js/components/models-tab/model-picker.js +212 -0
- package/lib/public/js/components/models-tab/use-models.js +17 -14
- package/lib/public/js/components/onboarding/use-welcome-pairing.js +4 -4
- package/lib/public/js/components/onboarding/welcome-pairing-step.js +2 -2
- package/lib/public/js/components/overflow-menu.js +122 -0
- package/lib/public/js/components/pairings.js +36 -8
- package/lib/public/js/components/routes/agents-route.js +27 -0
- package/lib/public/js/components/routes/general-route.js +2 -0
- package/lib/public/js/components/routes/index.js +1 -0
- package/lib/public/js/components/routes/telegram-route.js +2 -2
- package/lib/public/js/components/secret-input.js +8 -1
- package/lib/public/js/components/sidebar.js +65 -39
- package/lib/public/js/components/telegram-workspace/index.js +175 -74
- package/lib/public/js/components/telegram-workspace/manage.js +83 -10
- package/lib/public/js/components/telegram-workspace/onboarding.js +9 -8
- package/lib/public/js/components/webhooks.js +43 -18
- package/lib/public/js/hooks/use-app-shell-controller.js +7 -0
- package/lib/public/js/hooks/use-browse-navigation.js +8 -5
- package/lib/public/js/hooks/use-destination-session-selection.js +8 -1
- package/lib/public/js/lib/api.js +163 -9
- package/lib/public/js/lib/app-navigation.js +2 -1
- package/lib/public/js/lib/channel-create-operation.js +102 -0
- package/lib/public/js/lib/format.js +14 -0
- package/lib/public/js/lib/sse.js +51 -0
- package/lib/public/js/lib/telegram-api.js +38 -18
- package/lib/public/setup.html +1 -0
- package/lib/public/shared/browse-file-policies.json +0 -1
- package/lib/server/agents/service.js +1478 -0
- package/lib/server/constants.js +2 -2
- package/lib/server/env.js +3 -1
- package/lib/server/gateway.js +104 -20
- package/lib/server/gmail-serve.js +2 -12
- package/lib/server/gmail-watch.js +29 -2
- package/lib/server/onboarding/import/import-applier.js +0 -1
- package/lib/server/onboarding/index.js +0 -6
- package/lib/server/onboarding/workspace.js +74 -38
- package/lib/server/openclaw-config.js +23 -0
- package/lib/server/operation-events.js +141 -0
- package/lib/server/routes/agents.js +266 -0
- package/lib/server/routes/pairings.js +135 -25
- package/lib/server/routes/system.js +90 -10
- package/lib/server/routes/telegram.js +247 -51
- package/lib/server/startup.js +23 -0
- package/lib/server/telegram-workspace.js +61 -10
- package/lib/server/topic-registry.js +66 -7
- package/lib/server/watchdog.js +151 -27
- package/lib/server/webhooks.js +60 -12
- package/lib/server.js +40 -27
- package/lib/setup/core-prompts/AGENTS.md +6 -5
- package/lib/setup/core-prompts/TOOLS.md +1 -8
- package/package.json +1 -1
- package/lib/setup/skills/control-ui/SKILL.md +0 -62
|
@@ -2,6 +2,14 @@ const fs = require("fs");
|
|
|
2
2
|
const { WORKSPACE_DIR } = require("./constants");
|
|
3
3
|
|
|
4
4
|
const kRegistryPath = `${WORKSPACE_DIR}/topic-registry.json`;
|
|
5
|
+
const kDefaultAccountId = "default";
|
|
6
|
+
const kDefaultAgentId = "default";
|
|
7
|
+
|
|
8
|
+
const normalizeAccountId = (value) =>
|
|
9
|
+
String(value || "").trim() || kDefaultAccountId;
|
|
10
|
+
|
|
11
|
+
const normalizeGroupAgentId = (value) =>
|
|
12
|
+
String(value || "").trim() || kDefaultAgentId;
|
|
5
13
|
|
|
6
14
|
const readRegistry = () => {
|
|
7
15
|
try {
|
|
@@ -36,6 +44,20 @@ const setGroup = (groupId, groupData) => {
|
|
|
36
44
|
return registry;
|
|
37
45
|
};
|
|
38
46
|
|
|
47
|
+
const getGroupsForAccount = (accountId) => {
|
|
48
|
+
const registry = readRegistry();
|
|
49
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
50
|
+
const groups = registry.groups && typeof registry.groups === "object"
|
|
51
|
+
? registry.groups
|
|
52
|
+
: {};
|
|
53
|
+
return Object.fromEntries(
|
|
54
|
+
Object.entries(groups).filter(([, group]) => {
|
|
55
|
+
const groupAccountId = normalizeAccountId(group?.accountId);
|
|
56
|
+
return groupAccountId === normalizedAccountId;
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
39
61
|
const addTopic = (groupId, threadId, topicData) => {
|
|
40
62
|
const registry = readRegistry();
|
|
41
63
|
if (!registry.groups[groupId]) {
|
|
@@ -90,21 +112,56 @@ const getTotalTopicCount = () => {
|
|
|
90
112
|
return count;
|
|
91
113
|
};
|
|
92
114
|
|
|
93
|
-
|
|
94
|
-
const renderTopicRegistryMarkdown = ({ includeSyncGuidance = false } = {}) => {
|
|
115
|
+
const getTopicsForAgent = (agentId) => {
|
|
95
116
|
const registry = readRegistry();
|
|
117
|
+
const groups = registry.groups && typeof registry.groups === "object"
|
|
118
|
+
? registry.groups
|
|
119
|
+
: {};
|
|
120
|
+
const normalizedAgentId = normalizeGroupAgentId(agentId);
|
|
96
121
|
const rows = [];
|
|
97
|
-
for (const [groupId, group] of Object.entries(
|
|
98
|
-
|
|
122
|
+
for (const [groupId, group] of Object.entries(groups)) {
|
|
123
|
+
const groupAgentId = normalizeGroupAgentId(group?.agentId);
|
|
124
|
+
const groupName = String(group?.name || "").trim() || groupId;
|
|
125
|
+
const topics = group?.topics && typeof group.topics === "object"
|
|
126
|
+
? group.topics
|
|
127
|
+
: {};
|
|
128
|
+
const isGroupOwner = groupAgentId === normalizedAgentId;
|
|
129
|
+
for (const [threadId, topic] of Object.entries(topics)) {
|
|
130
|
+
const topicAgentId = String(topic?.agentId || "").trim();
|
|
131
|
+
if (!isGroupOwner && topicAgentId !== normalizedAgentId) continue;
|
|
99
132
|
rows.push({
|
|
100
|
-
groupName
|
|
133
|
+
groupName,
|
|
101
134
|
groupId,
|
|
102
|
-
topicName: topic
|
|
135
|
+
topicName: topic?.name,
|
|
103
136
|
threadId,
|
|
137
|
+
groupAgentId,
|
|
138
|
+
topicAgentId,
|
|
104
139
|
});
|
|
105
140
|
}
|
|
106
141
|
}
|
|
107
|
-
|
|
142
|
+
return rows;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Render the topic registry as a markdown section for TOOLS.md
|
|
146
|
+
const renderTopicRegistryMarkdown = ({
|
|
147
|
+
includeSyncGuidance = false,
|
|
148
|
+
agentId = "",
|
|
149
|
+
} = {}) => {
|
|
150
|
+
const registry = readRegistry();
|
|
151
|
+
const groups = registry.groups && typeof registry.groups === "object"
|
|
152
|
+
? registry.groups
|
|
153
|
+
: {};
|
|
154
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
155
|
+
const rows = normalizedAgentId
|
|
156
|
+
? getTopicsForAgent(normalizedAgentId)
|
|
157
|
+
: Object.entries(groups).flatMap(([groupId, group]) =>
|
|
158
|
+
Object.entries(group.topics || {}).map(([threadId, topic]) => ({
|
|
159
|
+
groupName: group.name || groupId,
|
|
160
|
+
groupId,
|
|
161
|
+
topicName: topic.name,
|
|
162
|
+
threadId,
|
|
163
|
+
})));
|
|
164
|
+
if (rows.length === 0) return "";
|
|
108
165
|
|
|
109
166
|
const lines = [
|
|
110
167
|
"",
|
|
@@ -144,9 +201,11 @@ module.exports = {
|
|
|
144
201
|
writeRegistry,
|
|
145
202
|
getGroup,
|
|
146
203
|
setGroup,
|
|
204
|
+
getGroupsForAccount,
|
|
147
205
|
addTopic,
|
|
148
206
|
updateTopic,
|
|
149
207
|
removeTopic,
|
|
150
208
|
getTotalTopicCount,
|
|
209
|
+
getTopicsForAgent,
|
|
151
210
|
renderTopicRegistryMarkdown,
|
|
152
211
|
};
|
package/lib/server/watchdog.js
CHANGED
|
@@ -12,10 +12,15 @@ const kBootstrapHealthCheckMs = 5 * 1000;
|
|
|
12
12
|
const kExpectedRestartWindowMs = 15 * 1000;
|
|
13
13
|
|
|
14
14
|
const isTruthy = (value) =>
|
|
15
|
-
["1", "true", "yes", "on"].includes(
|
|
15
|
+
["1", "true", "yes", "on"].includes(
|
|
16
|
+
String(value || "")
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase(),
|
|
19
|
+
);
|
|
16
20
|
|
|
17
21
|
const parseHealthResult = (result) => {
|
|
18
|
-
if (!result?.ok)
|
|
22
|
+
if (!result?.ok)
|
|
23
|
+
return { ok: false, reason: result?.stderr || "health command failed" };
|
|
19
24
|
const raw = String(result.stdout || "").trim();
|
|
20
25
|
if (!raw) return { ok: true };
|
|
21
26
|
try {
|
|
@@ -39,6 +44,18 @@ const parseHealthResult = (result) => {
|
|
|
39
44
|
}
|
|
40
45
|
return { ok: true };
|
|
41
46
|
};
|
|
47
|
+
const isDuplicateGatewayLaunchExit = ({ code, stderrTail = [] } = {}) => {
|
|
48
|
+
if (code !== 1) return false;
|
|
49
|
+
const stderrText = (Array.isArray(stderrTail) ? stderrTail : [])
|
|
50
|
+
.map((entry) => String(entry || ""))
|
|
51
|
+
.join("\n")
|
|
52
|
+
.toLowerCase();
|
|
53
|
+
if (!stderrText) return false;
|
|
54
|
+
return (
|
|
55
|
+
stderrText.includes("another gateway instance is already listening") ||
|
|
56
|
+
(stderrText.includes("port") && stderrText.includes("already in use"))
|
|
57
|
+
);
|
|
58
|
+
};
|
|
42
59
|
|
|
43
60
|
const createWatchdog = ({
|
|
44
61
|
clawCmd,
|
|
@@ -58,7 +75,9 @@ const createWatchdog = ({
|
|
|
58
75
|
repairAttempts: 0,
|
|
59
76
|
crashTimestamps: [],
|
|
60
77
|
autoRepair: isTruthy(process.env.WATCHDOG_AUTO_REPAIR),
|
|
61
|
-
notificationsDisabled: isTruthy(
|
|
78
|
+
notificationsDisabled: isTruthy(
|
|
79
|
+
process.env.WATCHDOG_NOTIFICATIONS_DISABLED,
|
|
80
|
+
),
|
|
62
81
|
operationInProgress: false,
|
|
63
82
|
gatewayStartedAt: null,
|
|
64
83
|
gatewayPid: null,
|
|
@@ -92,7 +111,8 @@ const createWatchdog = ({
|
|
|
92
111
|
scheduleDegradedHealthCheck();
|
|
93
112
|
}
|
|
94
113
|
}, kWatchdogDegradedCheckIntervalMs);
|
|
95
|
-
if (typeof degradedHealthTimer.unref === "function")
|
|
114
|
+
if (typeof degradedHealthTimer.unref === "function")
|
|
115
|
+
degradedHealthTimer.unref();
|
|
96
116
|
};
|
|
97
117
|
|
|
98
118
|
const clearExpectedRestartWindow = () => {
|
|
@@ -101,7 +121,10 @@ const createWatchdog = ({
|
|
|
101
121
|
};
|
|
102
122
|
|
|
103
123
|
const markExpectedRestartWindow = (durationMs = kExpectedRestartWindowMs) => {
|
|
104
|
-
const safeDuration = Math.max(
|
|
124
|
+
const safeDuration = Math.max(
|
|
125
|
+
5000,
|
|
126
|
+
Number(durationMs) || kExpectedRestartWindowMs,
|
|
127
|
+
);
|
|
105
128
|
state.expectedRestartInProgress = true;
|
|
106
129
|
state.expectedRestartUntilMs = Date.now() + safeDuration;
|
|
107
130
|
};
|
|
@@ -142,13 +165,21 @@ const createWatchdog = ({
|
|
|
142
165
|
|
|
143
166
|
const trimCrashWindow = () => {
|
|
144
167
|
const threshold = Date.now() - kWatchdogCrashLoopWindowMs;
|
|
145
|
-
state.crashTimestamps = state.crashTimestamps.filter(
|
|
168
|
+
state.crashTimestamps = state.crashTimestamps.filter(
|
|
169
|
+
(ts) => ts >= threshold,
|
|
170
|
+
);
|
|
146
171
|
};
|
|
147
172
|
|
|
148
173
|
const createCorrelationId = () =>
|
|
149
174
|
`${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
150
175
|
|
|
151
|
-
const logEvent = (
|
|
176
|
+
const logEvent = (
|
|
177
|
+
eventType,
|
|
178
|
+
source,
|
|
179
|
+
status,
|
|
180
|
+
details = null,
|
|
181
|
+
correlationId = "",
|
|
182
|
+
) => {
|
|
152
183
|
try {
|
|
153
184
|
insertWatchdogEvent({
|
|
154
185
|
eventType,
|
|
@@ -168,16 +199,25 @@ const createWatchdog = ({
|
|
|
168
199
|
}
|
|
169
200
|
if (!notifier?.notify) return { ok: false, reason: "notifier_unavailable" };
|
|
170
201
|
const result = await notifier.notify(message);
|
|
171
|
-
logEvent(
|
|
202
|
+
logEvent(
|
|
203
|
+
"notification",
|
|
204
|
+
"watchdog",
|
|
205
|
+
result.ok ? "ok" : "failed",
|
|
206
|
+
result,
|
|
207
|
+
correlationId,
|
|
208
|
+
);
|
|
172
209
|
return result;
|
|
173
210
|
};
|
|
174
211
|
|
|
175
212
|
const getWatchdogSetupUrl = () => {
|
|
176
213
|
try {
|
|
177
214
|
const base =
|
|
178
|
-
typeof resolveSetupUrl === "function"
|
|
215
|
+
typeof resolveSetupUrl === "function"
|
|
216
|
+
? String(resolveSetupUrl() || "")
|
|
217
|
+
: "";
|
|
179
218
|
if (base) return `${base.replace(/\/+$/, "")}/#/watchdog`;
|
|
180
|
-
const fallbackPort =
|
|
219
|
+
const fallbackPort =
|
|
220
|
+
Number.parseInt(String(process.env.PORT || "3000"), 10) || 3000;
|
|
181
221
|
return `http://localhost:${fallbackPort}/#/watchdog`;
|
|
182
222
|
} catch {
|
|
183
223
|
return "";
|
|
@@ -190,7 +230,8 @@ const createWatchdog = ({
|
|
|
190
230
|
return `${line} - [View logs](${setupUrl})`;
|
|
191
231
|
};
|
|
192
232
|
|
|
193
|
-
const asInlineCode = (value) =>
|
|
233
|
+
const asInlineCode = (value) =>
|
|
234
|
+
`\`${String(value || "").replace(/`/g, "")}\``;
|
|
194
235
|
|
|
195
236
|
const notifyAutoRepairOutcome = async ({
|
|
196
237
|
source,
|
|
@@ -225,11 +266,15 @@ const createWatchdog = ({
|
|
|
225
266
|
const hasAutoRepair = typeof autoRepair === "boolean";
|
|
226
267
|
const hasNotificationsEnabled = typeof notificationsEnabled === "boolean";
|
|
227
268
|
if (!hasAutoRepair && !hasNotificationsEnabled) {
|
|
228
|
-
throw new Error(
|
|
269
|
+
throw new Error(
|
|
270
|
+
"Expected autoRepair and/or notificationsEnabled boolean",
|
|
271
|
+
);
|
|
229
272
|
}
|
|
230
273
|
const envVars = readEnvFile();
|
|
231
274
|
if (hasAutoRepair) {
|
|
232
|
-
const existingIdx = envVars.findIndex(
|
|
275
|
+
const existingIdx = envVars.findIndex(
|
|
276
|
+
(item) => item.key === "WATCHDOG_AUTO_REPAIR",
|
|
277
|
+
);
|
|
233
278
|
const nextValue = autoRepair ? "true" : "false";
|
|
234
279
|
if (existingIdx >= 0) {
|
|
235
280
|
envVars[existingIdx] = { ...envVars[existingIdx], value: nextValue };
|
|
@@ -254,7 +299,9 @@ const createWatchdog = ({
|
|
|
254
299
|
writeEnvFile(envVars);
|
|
255
300
|
reloadEnv();
|
|
256
301
|
state.autoRepair = isTruthy(process.env.WATCHDOG_AUTO_REPAIR);
|
|
257
|
-
state.notificationsDisabled = isTruthy(
|
|
302
|
+
state.notificationsDisabled = isTruthy(
|
|
303
|
+
process.env.WATCHDOG_NOTIFICATIONS_DISABLED,
|
|
304
|
+
);
|
|
258
305
|
return getSettings();
|
|
259
306
|
};
|
|
260
307
|
|
|
@@ -277,7 +324,13 @@ const createWatchdog = ({
|
|
|
277
324
|
const child = launchGatewayProcess();
|
|
278
325
|
launchedGateway = !!child;
|
|
279
326
|
if (launchedGateway) {
|
|
280
|
-
logEvent(
|
|
327
|
+
logEvent(
|
|
328
|
+
"restart",
|
|
329
|
+
"repair",
|
|
330
|
+
"ok",
|
|
331
|
+
{ pid: child.pid },
|
|
332
|
+
correlationId,
|
|
333
|
+
);
|
|
281
334
|
} else {
|
|
282
335
|
logEvent(
|
|
283
336
|
"restart",
|
|
@@ -288,7 +341,13 @@ const createWatchdog = ({
|
|
|
288
341
|
);
|
|
289
342
|
}
|
|
290
343
|
} catch (err) {
|
|
291
|
-
logEvent(
|
|
344
|
+
logEvent(
|
|
345
|
+
"restart",
|
|
346
|
+
"repair",
|
|
347
|
+
"failed",
|
|
348
|
+
{ error: err.message },
|
|
349
|
+
correlationId,
|
|
350
|
+
);
|
|
292
351
|
}
|
|
293
352
|
state.health = "unknown";
|
|
294
353
|
state.lifecycle = "running";
|
|
@@ -344,7 +403,10 @@ const createWatchdog = ({
|
|
|
344
403
|
source = "health_timer",
|
|
345
404
|
allowAutoRepair = true,
|
|
346
405
|
} = {}) => {
|
|
347
|
-
if (
|
|
406
|
+
if (
|
|
407
|
+
state.expectedRestartInProgress &&
|
|
408
|
+
Date.now() >= state.expectedRestartUntilMs
|
|
409
|
+
) {
|
|
348
410
|
clearExpectedRestartWindow();
|
|
349
411
|
}
|
|
350
412
|
if (state.operationInProgress && !allowDuringOperation) return false;
|
|
@@ -358,7 +420,8 @@ const createWatchdog = ({
|
|
|
358
420
|
state.gatewayStartedAt != null &&
|
|
359
421
|
state.gatewayStartedAt !== gatewayStartedAtAtStart;
|
|
360
422
|
const restartWindowActive =
|
|
361
|
-
state.expectedRestartInProgress &&
|
|
423
|
+
state.expectedRestartInProgress &&
|
|
424
|
+
Date.now() < state.expectedRestartUntilMs;
|
|
362
425
|
if (staleAfterRestart) {
|
|
363
426
|
return false;
|
|
364
427
|
}
|
|
@@ -370,7 +433,8 @@ const createWatchdog = ({
|
|
|
370
433
|
clearExpectedRestartWindow();
|
|
371
434
|
state.health = "healthy";
|
|
372
435
|
state.lifecycle = "running";
|
|
373
|
-
if (!state.uptimeStartedAt || wasUnhealthy)
|
|
436
|
+
if (!state.uptimeStartedAt || wasUnhealthy)
|
|
437
|
+
state.uptimeStartedAt = Date.now();
|
|
374
438
|
state.repairAttempts = 0;
|
|
375
439
|
state.crashRecoveryActive = false;
|
|
376
440
|
if (recoveredFromCrashLoop) {
|
|
@@ -384,6 +448,13 @@ const createWatchdog = ({
|
|
|
384
448
|
},
|
|
385
449
|
correlationId,
|
|
386
450
|
);
|
|
451
|
+
await notify(
|
|
452
|
+
[
|
|
453
|
+
"🐺 *AlphaClaw Watchdog*",
|
|
454
|
+
withViewLogsSuffix("🟢 Gateway healthy again"),
|
|
455
|
+
].join("\n"),
|
|
456
|
+
correlationId,
|
|
457
|
+
);
|
|
387
458
|
}
|
|
388
459
|
if (state.pendingRecoveryNoticeSource) {
|
|
389
460
|
const recoverySource = state.pendingRecoveryNoticeSource;
|
|
@@ -392,12 +463,17 @@ const createWatchdog = ({
|
|
|
392
463
|
[
|
|
393
464
|
"🐺 *AlphaClaw Watchdog*",
|
|
394
465
|
withViewLogsSuffix("🟢 Gateway healthy again"),
|
|
395
|
-
`Trigger: ${asInlineCode(recoverySource)}`,
|
|
396
466
|
].join("\n"),
|
|
397
467
|
correlationId,
|
|
398
468
|
);
|
|
399
469
|
}
|
|
400
|
-
logEvent(
|
|
470
|
+
logEvent(
|
|
471
|
+
"health_check",
|
|
472
|
+
source,
|
|
473
|
+
"ok",
|
|
474
|
+
parsed.details || result,
|
|
475
|
+
correlationId,
|
|
476
|
+
);
|
|
401
477
|
return true;
|
|
402
478
|
}
|
|
403
479
|
if (restartWindowActive) {
|
|
@@ -445,7 +521,10 @@ const createWatchdog = ({
|
|
|
445
521
|
|
|
446
522
|
if (state.health === "unknown" && state.lifecycle === "running") {
|
|
447
523
|
state.startupConsecutiveHealthFailures += 1;
|
|
448
|
-
if (
|
|
524
|
+
if (
|
|
525
|
+
state.startupConsecutiveHealthFailures <
|
|
526
|
+
kWatchdogStartupFailureThreshold
|
|
527
|
+
) {
|
|
449
528
|
logEvent(
|
|
450
529
|
"health_check",
|
|
451
530
|
source,
|
|
@@ -486,7 +565,13 @@ const createWatchdog = ({
|
|
|
486
565
|
try {
|
|
487
566
|
const child = launchGatewayProcess();
|
|
488
567
|
if (child) {
|
|
489
|
-
logEvent(
|
|
568
|
+
logEvent(
|
|
569
|
+
"restart",
|
|
570
|
+
"exit_event",
|
|
571
|
+
"ok",
|
|
572
|
+
{ pid: child.pid },
|
|
573
|
+
correlationId,
|
|
574
|
+
);
|
|
490
575
|
} else {
|
|
491
576
|
logEvent(
|
|
492
577
|
"restart",
|
|
@@ -497,18 +582,30 @@ const createWatchdog = ({
|
|
|
497
582
|
);
|
|
498
583
|
}
|
|
499
584
|
} catch (err) {
|
|
500
|
-
logEvent(
|
|
585
|
+
logEvent(
|
|
586
|
+
"restart",
|
|
587
|
+
"exit_event",
|
|
588
|
+
"failed",
|
|
589
|
+
{ error: err.message },
|
|
590
|
+
correlationId,
|
|
591
|
+
);
|
|
501
592
|
} finally {
|
|
502
593
|
state.operationInProgress = false;
|
|
503
594
|
}
|
|
504
595
|
};
|
|
505
596
|
|
|
506
|
-
const onGatewayExit = ({
|
|
597
|
+
const onGatewayExit = ({
|
|
598
|
+
code,
|
|
599
|
+
signal,
|
|
600
|
+
expectedExit = false,
|
|
601
|
+
stderrTail = [],
|
|
602
|
+
} = {}) => {
|
|
507
603
|
const correlationId = createCorrelationId();
|
|
508
604
|
clearDegradedHealthCheckTimer();
|
|
509
605
|
if (expectedExit && (code == null || code === 0)) {
|
|
510
606
|
state.lifecycle = "restarting";
|
|
511
607
|
state.health = "unknown";
|
|
608
|
+
state.uptimeStartedAt = null;
|
|
512
609
|
state.crashRecoveryActive = false;
|
|
513
610
|
markExpectedRestartWindow();
|
|
514
611
|
startBootstrapHealthChecks();
|
|
@@ -521,9 +618,33 @@ const createWatchdog = ({
|
|
|
521
618
|
);
|
|
522
619
|
return;
|
|
523
620
|
}
|
|
621
|
+
if (isDuplicateGatewayLaunchExit({ code, stderrTail })) {
|
|
622
|
+
state.lifecycle = "running";
|
|
623
|
+
state.health = "unknown";
|
|
624
|
+
state.crashRecoveryActive = false;
|
|
625
|
+
state.startupConsecutiveHealthFailures = 0;
|
|
626
|
+
if (!state.uptimeStartedAt) {
|
|
627
|
+
state.uptimeStartedAt = Date.now();
|
|
628
|
+
}
|
|
629
|
+
startBootstrapHealthChecks();
|
|
630
|
+
logEvent(
|
|
631
|
+
"restart",
|
|
632
|
+
"exit_event",
|
|
633
|
+
"ok",
|
|
634
|
+
{
|
|
635
|
+
duplicateLaunch: true,
|
|
636
|
+
code: code ?? null,
|
|
637
|
+
signal: signal ?? null,
|
|
638
|
+
stderrTail,
|
|
639
|
+
},
|
|
640
|
+
correlationId,
|
|
641
|
+
);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
524
644
|
|
|
525
645
|
state.lifecycle = "crashed";
|
|
526
646
|
state.health = "unhealthy";
|
|
647
|
+
state.uptimeStartedAt = null;
|
|
527
648
|
state.crashRecoveryActive = true;
|
|
528
649
|
state.crashTimestamps.push(Date.now());
|
|
529
650
|
trimCrashWindow();
|
|
@@ -557,7 +678,9 @@ const createWatchdog = ({
|
|
|
557
678
|
),
|
|
558
679
|
`Crashes: ${state.crashTimestamps.length} in the last ${Math.floor(kWatchdogCrashLoopWindowMs / 1000)}s`,
|
|
559
680
|
`Last exit code: ${code ?? "unknown"}`,
|
|
560
|
-
...(state.autoRepair
|
|
681
|
+
...(state.autoRepair
|
|
682
|
+
? []
|
|
683
|
+
: ["Auto-restart paused; manual action required."]),
|
|
561
684
|
].join("\n"),
|
|
562
685
|
correlationId,
|
|
563
686
|
);
|
|
@@ -591,6 +714,7 @@ const createWatchdog = ({
|
|
|
591
714
|
clearDegradedHealthCheckTimer();
|
|
592
715
|
state.lifecycle = "restarting";
|
|
593
716
|
state.health = "unknown";
|
|
717
|
+
state.uptimeStartedAt = null;
|
|
594
718
|
state.startupConsecutiveHealthFailures = 0;
|
|
595
719
|
state.crashRecoveryActive = false;
|
|
596
720
|
markExpectedRestartWindow();
|
|
@@ -612,7 +736,6 @@ const createWatchdog = ({
|
|
|
612
736
|
state.lifecycle = "running";
|
|
613
737
|
state.health = "unknown";
|
|
614
738
|
state.startupConsecutiveHealthFailures = 0;
|
|
615
|
-
state.uptimeStartedAt = Date.now();
|
|
616
739
|
state.gatewayStartedAt = Date.now();
|
|
617
740
|
startBootstrapHealthChecks();
|
|
618
741
|
};
|
|
@@ -628,6 +751,7 @@ const createWatchdog = ({
|
|
|
628
751
|
healthTimer = null;
|
|
629
752
|
}
|
|
630
753
|
state.lifecycle = "stopped";
|
|
754
|
+
state.uptimeStartedAt = null;
|
|
631
755
|
state.startupConsecutiveHealthFailures = 0;
|
|
632
756
|
};
|
|
633
757
|
|
package/lib/server/webhooks.js
CHANGED
|
@@ -99,11 +99,16 @@ const normalizeDestination = (destination = null) => {
|
|
|
99
99
|
if (!destination || typeof destination !== "object") return null;
|
|
100
100
|
const channel = String(destination?.channel || "").trim();
|
|
101
101
|
const to = String(destination?.to || "").trim();
|
|
102
|
+
const agentId = String(destination?.agentId || "").trim();
|
|
102
103
|
if (!channel && !to) return null;
|
|
103
104
|
if (!channel || !to) {
|
|
104
105
|
throw new Error("destination.channel and destination.to are required");
|
|
105
106
|
}
|
|
106
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
channel,
|
|
109
|
+
to,
|
|
110
|
+
...(agentId ? { agentId } : {}),
|
|
111
|
+
};
|
|
107
112
|
};
|
|
108
113
|
|
|
109
114
|
const resolveTransformPathFromMapping = (name, mapping) => {
|
|
@@ -137,8 +142,7 @@ const normalizeMappingTransformModules = (mappings) => {
|
|
|
137
142
|
return changed;
|
|
138
143
|
};
|
|
139
144
|
|
|
140
|
-
const buildDefaultTransformSource = (name
|
|
141
|
-
const normalizedDestination = normalizeDestination(destination);
|
|
145
|
+
const buildDefaultTransformSource = (name) => {
|
|
142
146
|
return [
|
|
143
147
|
"export default async function transform(payload, context) {",
|
|
144
148
|
" const data = payload.payload || payload;",
|
|
@@ -146,12 +150,6 @@ const buildDefaultTransformSource = (name, destination = null) => {
|
|
|
146
150
|
" message: data.message,",
|
|
147
151
|
` name: data.name || "${name}",`,
|
|
148
152
|
' wakeMode: data.wakeMode || "now",',
|
|
149
|
-
...(normalizedDestination
|
|
150
|
-
? [
|
|
151
|
-
` channel: ${JSON.stringify(normalizedDestination.channel)},`,
|
|
152
|
-
` to: ${JSON.stringify(normalizedDestination.to)},`,
|
|
153
|
-
]
|
|
154
|
-
: []),
|
|
155
153
|
" };",
|
|
156
154
|
"}",
|
|
157
155
|
"",
|
|
@@ -164,6 +162,7 @@ const ensureWebhookTransform = ({
|
|
|
164
162
|
name,
|
|
165
163
|
source = "",
|
|
166
164
|
destination = null,
|
|
165
|
+
forceWrite = false,
|
|
167
166
|
}) => {
|
|
168
167
|
const webhookName = validateWebhookName(name);
|
|
169
168
|
const transformAbsolutePath = getTransformAbsolutePath(
|
|
@@ -171,14 +170,14 @@ const ensureWebhookTransform = ({
|
|
|
171
170
|
webhookName,
|
|
172
171
|
);
|
|
173
172
|
fs.mkdirSync(path.dirname(transformAbsolutePath), { recursive: true });
|
|
174
|
-
if (fs.existsSync(transformAbsolutePath)) {
|
|
173
|
+
if (fs.existsSync(transformAbsolutePath) && !forceWrite) {
|
|
175
174
|
return { changed: false, path: transformAbsolutePath };
|
|
176
175
|
}
|
|
177
176
|
fs.writeFileSync(
|
|
178
177
|
transformAbsolutePath,
|
|
179
178
|
String(source || "").trim()
|
|
180
179
|
? `${String(source).replace(/\s+$/, "")}\n`
|
|
181
|
-
: buildDefaultTransformSource(webhookName
|
|
180
|
+
: buildDefaultTransformSource(webhookName),
|
|
182
181
|
);
|
|
183
182
|
return { changed: true, path: transformAbsolutePath };
|
|
184
183
|
};
|
|
@@ -235,6 +234,27 @@ const ensureWebhookMapping = ({ cfg, name, mapping = {} }) => {
|
|
|
235
234
|
};
|
|
236
235
|
};
|
|
237
236
|
|
|
237
|
+
const resolveDefaultAgentId = (cfg) => {
|
|
238
|
+
const agents = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
|
|
239
|
+
const explicitDefault = agents.find((entry) => !!entry?.default);
|
|
240
|
+
const defaultId = String(explicitDefault?.id || "").trim();
|
|
241
|
+
if (defaultId) return defaultId;
|
|
242
|
+
const firstId = String(agents[0]?.id || "").trim();
|
|
243
|
+
return firstId || "main";
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const resolveWebhookAgentId = ({ cfg, requestedAgentId = "" }) => {
|
|
247
|
+
const normalizedRequested = String(requestedAgentId || "").trim();
|
|
248
|
+
const agents = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
|
|
249
|
+
if (
|
|
250
|
+
normalizedRequested &&
|
|
251
|
+
agents.some((entry) => String(entry?.id || "").trim() === normalizedRequested)
|
|
252
|
+
) {
|
|
253
|
+
return normalizedRequested;
|
|
254
|
+
}
|
|
255
|
+
return resolveDefaultAgentId(cfg);
|
|
256
|
+
};
|
|
257
|
+
|
|
238
258
|
const listManagedWebhooksFromConfig = ({ cfg }) => {
|
|
239
259
|
const presets = Array.isArray(cfg?.hooks?.presets) ? cfg.hooks.presets : [];
|
|
240
260
|
return kManagedWebhookConfigs
|
|
@@ -289,6 +309,10 @@ const listWebhooks = ({ fs, constants }) => {
|
|
|
289
309
|
path: `/hooks/${name}`,
|
|
290
310
|
transformPath,
|
|
291
311
|
transformExists: fs.existsSync(transformAbsolutePath),
|
|
312
|
+
deliver: Boolean(mapping?.deliver),
|
|
313
|
+
channel: String(mapping?.channel || "").trim(),
|
|
314
|
+
to: String(mapping?.to || "").trim(),
|
|
315
|
+
agentId: String(mapping?.agentId || "").trim(),
|
|
292
316
|
managed: Boolean(managed),
|
|
293
317
|
managedReason: managed?.managedReason || "",
|
|
294
318
|
};
|
|
@@ -332,6 +356,7 @@ const createWebhook = ({
|
|
|
332
356
|
mapping = {},
|
|
333
357
|
transformSource = "",
|
|
334
358
|
destination = null,
|
|
359
|
+
overwriteTransform = false,
|
|
335
360
|
}) => {
|
|
336
361
|
const webhookName = validateWebhookName(name);
|
|
337
362
|
const normalizedDestination = normalizeDestination(destination);
|
|
@@ -346,10 +371,32 @@ const createWebhook = ({
|
|
|
346
371
|
if (exists && !upsert) {
|
|
347
372
|
throw new Error(`Webhook "${webhookName}" already exists`);
|
|
348
373
|
}
|
|
374
|
+
const agentId = resolveWebhookAgentId({
|
|
375
|
+
cfg,
|
|
376
|
+
requestedAgentId:
|
|
377
|
+
String(mapping?.agentId || "").trim() ||
|
|
378
|
+
String(normalizedDestination?.agentId || "").trim(),
|
|
379
|
+
});
|
|
380
|
+
const resolvedMapping = {
|
|
381
|
+
...mapping,
|
|
382
|
+
deliver: true,
|
|
383
|
+
channel:
|
|
384
|
+
String(mapping?.channel || "").trim() ||
|
|
385
|
+
String(normalizedDestination?.channel || "").trim() ||
|
|
386
|
+
"last",
|
|
387
|
+
...(String(mapping?.to || "").trim() || String(normalizedDestination?.to || "").trim()
|
|
388
|
+
? {
|
|
389
|
+
to:
|
|
390
|
+
String(mapping?.to || "").trim() ||
|
|
391
|
+
String(normalizedDestination?.to || "").trim(),
|
|
392
|
+
}
|
|
393
|
+
: {}),
|
|
394
|
+
agentId,
|
|
395
|
+
};
|
|
349
396
|
const ensuredMapping = ensureWebhookMapping({
|
|
350
397
|
cfg,
|
|
351
398
|
name: webhookName,
|
|
352
|
-
mapping,
|
|
399
|
+
mapping: resolvedMapping,
|
|
353
400
|
});
|
|
354
401
|
const ensuredTransform = ensureWebhookTransform({
|
|
355
402
|
fs,
|
|
@@ -357,6 +404,7 @@ const createWebhook = ({
|
|
|
357
404
|
name: webhookName,
|
|
358
405
|
source: transformSource,
|
|
359
406
|
destination: normalizedDestination,
|
|
407
|
+
forceWrite: overwriteTransform,
|
|
360
408
|
});
|
|
361
409
|
if (ensuredMapping.changed || ensuredTransform.changed || !exists) {
|
|
362
410
|
writeConfig({ fs, configPath, cfg });
|