@chrysb/alphaclaw 0.4.4 → 0.4.5
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/server/constants.js +22 -26
- package/lib/server/gmail-push.js +102 -6
- package/lib/server/gmail-watch.js +5 -20
- package/lib/server/helpers.js +5 -21
- package/lib/server/routes/google.js +2 -10
- package/lib/server/routes/telegram.js +3 -14
- package/lib/server/routes/usage.js +1 -5
- package/lib/server/routes/webhooks.js +2 -6
- package/lib/server/utils/boolean.js +22 -0
- package/lib/server/utils/json.js +31 -0
- package/lib/server/utils/network.js +5 -0
- package/lib/server/utils/number.js +8 -0
- package/lib/server/utils/shell.js +16 -0
- package/lib/server/webhook-middleware.js +1 -2
- package/package.json +1 -1
package/lib/server/constants.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
const os = require("os");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const kBrowseFilePolicies = require("../public/shared/browse-file-policies.json");
|
|
4
|
-
|
|
5
|
-
const parsePositiveIntEnv = (value, fallbackValue) => {
|
|
6
|
-
const parsed = Number.parseInt(String(value || ""), 10);
|
|
7
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackValue;
|
|
8
|
-
};
|
|
4
|
+
const { parsePositiveInt } = require("./utils/number");
|
|
9
5
|
|
|
10
6
|
// Portable root directory: --root-dir flag sets ALPHACLAW_ROOT_DIR before require
|
|
11
7
|
const kRootDir =
|
|
@@ -38,29 +34,29 @@ const CODEX_OAUTH_SCOPE = "openid profile email offline_access";
|
|
|
38
34
|
const CODEX_JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
39
35
|
const kCodexOauthStateTtlMs = 10 * 60 * 1000;
|
|
40
36
|
|
|
41
|
-
const kTrustProxyHops =
|
|
42
|
-
const kLoginWindowMs =
|
|
37
|
+
const kTrustProxyHops = parsePositiveInt(process.env.TRUST_PROXY_HOPS, 1);
|
|
38
|
+
const kLoginWindowMs = parsePositiveInt(
|
|
43
39
|
process.env.LOGIN_RATE_WINDOW_MS,
|
|
44
40
|
10 * 60 * 1000,
|
|
45
41
|
);
|
|
46
|
-
const kLoginMaxAttempts =
|
|
42
|
+
const kLoginMaxAttempts = parsePositiveInt(
|
|
47
43
|
process.env.LOGIN_RATE_MAX_ATTEMPTS,
|
|
48
44
|
5,
|
|
49
45
|
);
|
|
50
|
-
const kLoginBaseLockMs =
|
|
46
|
+
const kLoginBaseLockMs = parsePositiveInt(
|
|
51
47
|
process.env.LOGIN_RATE_BASE_LOCK_MS,
|
|
52
48
|
60 * 1000,
|
|
53
49
|
);
|
|
54
|
-
const kLoginMaxLockMs =
|
|
50
|
+
const kLoginMaxLockMs = parsePositiveInt(
|
|
55
51
|
process.env.LOGIN_RATE_MAX_LOCK_MS,
|
|
56
52
|
15 * 60 * 1000,
|
|
57
53
|
);
|
|
58
|
-
const kLoginCleanupIntervalMs =
|
|
54
|
+
const kLoginCleanupIntervalMs = parsePositiveInt(
|
|
59
55
|
process.env.LOGIN_RATE_CLEANUP_INTERVAL_MS,
|
|
60
56
|
60 * 1000,
|
|
61
57
|
);
|
|
62
58
|
const kLoginStateTtlMs = Math.max(
|
|
63
|
-
|
|
59
|
+
parsePositiveInt(
|
|
64
60
|
process.env.LOGIN_RATE_STATE_TTL_MS,
|
|
65
61
|
Math.max(kLoginWindowMs, kLoginMaxLockMs) * 3,
|
|
66
62
|
),
|
|
@@ -116,31 +112,31 @@ const kLatestVersionCacheTtlMs = 10 * 60 * 1000;
|
|
|
116
112
|
const kOpenclawRegistryUrl = "https://registry.npmjs.org/openclaw";
|
|
117
113
|
const kAlphaclawRegistryUrl = "https://registry.npmjs.org/@chrysb%2falphaclaw";
|
|
118
114
|
const kAppDir = kNpmPackageRoot;
|
|
119
|
-
const kMaxPayloadBytes =
|
|
120
|
-
const kWebhookPruneDays =
|
|
115
|
+
const kMaxPayloadBytes = parsePositiveInt(process.env.WEBHOOK_LOG_MAX_BYTES, 50 * 1024);
|
|
116
|
+
const kWebhookPruneDays = parsePositiveInt(process.env.WEBHOOK_LOG_RETENTION_DAYS, 30);
|
|
121
117
|
const kWatchdogCheckIntervalMs =
|
|
122
|
-
|
|
118
|
+
parsePositiveInt(process.env.WATCHDOG_CHECK_INTERVAL, 120) * 1000;
|
|
123
119
|
const kWatchdogDegradedCheckIntervalMs =
|
|
124
|
-
|
|
125
|
-
const kWatchdogStartupFailureThreshold =
|
|
120
|
+
parsePositiveInt(process.env.WATCHDOG_DEGRADED_CHECK_INTERVAL, 5) * 1000;
|
|
121
|
+
const kWatchdogStartupFailureThreshold = parsePositiveInt(
|
|
126
122
|
process.env.WATCHDOG_STARTUP_FAILURE_THRESHOLD,
|
|
127
123
|
3,
|
|
128
124
|
);
|
|
129
|
-
const kWatchdogMaxRepairAttempts =
|
|
125
|
+
const kWatchdogMaxRepairAttempts = parsePositiveInt(
|
|
130
126
|
process.env.WATCHDOG_MAX_REPAIR_ATTEMPTS,
|
|
131
127
|
2,
|
|
132
128
|
);
|
|
133
129
|
const kWatchdogCrashLoopWindowMs =
|
|
134
|
-
|
|
135
|
-
const kWatchdogCrashLoopThreshold =
|
|
130
|
+
parsePositiveInt(process.env.WATCHDOG_CRASH_LOOP_WINDOW, 300) * 1000;
|
|
131
|
+
const kWatchdogCrashLoopThreshold = parsePositiveInt(
|
|
136
132
|
process.env.WATCHDOG_CRASH_LOOP_THRESHOLD,
|
|
137
133
|
3,
|
|
138
134
|
);
|
|
139
|
-
const kWatchdogLogRetentionDays =
|
|
135
|
+
const kWatchdogLogRetentionDays = parsePositiveInt(
|
|
140
136
|
process.env.WATCHDOG_LOG_RETENTION_DAYS,
|
|
141
137
|
30,
|
|
142
138
|
);
|
|
143
|
-
const kLogMaxBytes =
|
|
139
|
+
const kLogMaxBytes = parsePositiveInt(
|
|
144
140
|
process.env.LOG_MAX_BYTES,
|
|
145
141
|
2 * 1024 * 1024,
|
|
146
142
|
);
|
|
@@ -266,17 +262,17 @@ const GOG_CREDENTIALS_PATH = path.join(GOG_CONFIG_DIR, "credentials.json");
|
|
|
266
262
|
const GOG_STATE_PATH = path.join(GOG_CONFIG_DIR, "state.json");
|
|
267
263
|
const GOG_KEYRING_PASSWORD = process.env.GOG_KEYRING_PASSWORD || "alphaclaw";
|
|
268
264
|
const kMaxGoogleAccounts = 5;
|
|
269
|
-
const kGmailServeBasePort =
|
|
265
|
+
const kGmailServeBasePort = parsePositiveInt(
|
|
270
266
|
process.env.GMAIL_SERVE_BASE_PORT,
|
|
271
267
|
18801,
|
|
272
268
|
);
|
|
273
269
|
const kGmailWatchRenewalIntervalMs =
|
|
274
|
-
|
|
270
|
+
parsePositiveInt(process.env.GMAIL_WATCH_RENEWAL_INTERVAL_SECONDS, 6 * 60 * 60) *
|
|
275
271
|
1000;
|
|
276
272
|
const kGmailWatchRenewalThresholdMs =
|
|
277
|
-
|
|
273
|
+
parsePositiveInt(process.env.GMAIL_WATCH_RENEWAL_THRESHOLD_SECONDS, 24 * 60 * 60) *
|
|
278
274
|
1000;
|
|
279
|
-
const kGmailMaxBodyBytes =
|
|
275
|
+
const kGmailMaxBodyBytes = parsePositiveInt(
|
|
280
276
|
process.env.GMAIL_WATCH_MAX_BODY_BYTES,
|
|
281
277
|
20000,
|
|
282
278
|
);
|
package/lib/server/gmail-push.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
const http = require("http");
|
|
2
|
+
const { parsePositiveInt } = require("./utils/number");
|
|
3
|
+
|
|
4
|
+
const kGmailPushDedupeWindowMs = parsePositiveInt(
|
|
5
|
+
process.env.GMAIL_PUSH_DEDUPE_WINDOW_MS,
|
|
6
|
+
24 * 60 * 60 * 1000,
|
|
7
|
+
);
|
|
8
|
+
const kGmailPushDedupeMaxEntries = parsePositiveInt(
|
|
9
|
+
process.env.GMAIL_PUSH_DEDUPE_MAX_ENTRIES,
|
|
10
|
+
50000,
|
|
11
|
+
);
|
|
2
12
|
|
|
3
13
|
const extractBodyBuffer = (body) => {
|
|
4
14
|
if (Buffer.isBuffer(body)) return body;
|
|
@@ -21,6 +31,68 @@ const parsePushEnvelope = (bodyBuffer) => {
|
|
|
21
31
|
};
|
|
22
32
|
};
|
|
23
33
|
|
|
34
|
+
const createPushEventDedupeKey = ({ envelope, payload }) => {
|
|
35
|
+
const messageId = String(
|
|
36
|
+
envelope?.message?.messageId || envelope?.message?.message_id || "",
|
|
37
|
+
).trim();
|
|
38
|
+
if (messageId) return `msg:${messageId}`;
|
|
39
|
+
const email = String(payload?.emailAddress || "")
|
|
40
|
+
.trim()
|
|
41
|
+
.toLowerCase();
|
|
42
|
+
const historyId = String(payload?.historyId || "").trim();
|
|
43
|
+
if (email && historyId) return `hist:${email}:${historyId}`;
|
|
44
|
+
if (historyId) return `hist:${historyId}`;
|
|
45
|
+
return "";
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const createGmailPushEventDeduper = ({
|
|
49
|
+
ttlMs = kGmailPushDedupeWindowMs,
|
|
50
|
+
maxEntries = kGmailPushDedupeMaxEntries,
|
|
51
|
+
} = {}) => {
|
|
52
|
+
const seenEvents = new Map();
|
|
53
|
+
|
|
54
|
+
const pruneExpiredEntries = (receivedAt) => {
|
|
55
|
+
const cutoff = receivedAt - ttlMs;
|
|
56
|
+
for (const [eventKey, seenAt] of seenEvents.entries()) {
|
|
57
|
+
if (seenAt > cutoff) break;
|
|
58
|
+
seenEvents.delete(eventKey);
|
|
59
|
+
}
|
|
60
|
+
while (seenEvents.size > maxEntries) {
|
|
61
|
+
const oldestKey = seenEvents.keys().next().value;
|
|
62
|
+
if (!oldestKey) break;
|
|
63
|
+
seenEvents.delete(oldestKey);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const shouldProcessPushEvent = ({ envelope, payload, receivedAt = Date.now() }) => {
|
|
68
|
+
const timestamp = Number.isFinite(receivedAt) ? receivedAt : Date.now();
|
|
69
|
+
pruneExpiredEntries(timestamp);
|
|
70
|
+
const eventKey = createPushEventDedupeKey({ envelope, payload });
|
|
71
|
+
if (!eventKey) return true;
|
|
72
|
+
return !seenEvents.has(eventKey);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
shouldProcessPushEvent.markProcessed = ({
|
|
76
|
+
envelope,
|
|
77
|
+
payload,
|
|
78
|
+
receivedAt = Date.now(),
|
|
79
|
+
}) => {
|
|
80
|
+
const timestamp = Number.isFinite(receivedAt) ? receivedAt : Date.now();
|
|
81
|
+
pruneExpiredEntries(timestamp);
|
|
82
|
+
const eventKey = createPushEventDedupeKey({ envelope, payload });
|
|
83
|
+
if (!eventKey) return true;
|
|
84
|
+
seenEvents.set(eventKey, timestamp);
|
|
85
|
+
return true;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return shouldProcessPushEvent;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const isSuccessfulProxyStatus = (statusCode) => {
|
|
92
|
+
const numericStatus = Number.parseInt(String(statusCode || 0), 10);
|
|
93
|
+
return numericStatus >= 200 && numericStatus < 300;
|
|
94
|
+
};
|
|
95
|
+
|
|
24
96
|
const proxyPushToServe = async ({
|
|
25
97
|
port,
|
|
26
98
|
bodyBuffer,
|
|
@@ -58,6 +130,8 @@ const createGmailPushHandler = ({
|
|
|
58
130
|
resolvePushToken,
|
|
59
131
|
resolveTargetByEmail,
|
|
60
132
|
markPushReceived,
|
|
133
|
+
shouldProcessPushEvent = createGmailPushEventDeduper(),
|
|
134
|
+
proxyPushToServeImpl = proxyPushToServe,
|
|
61
135
|
}) =>
|
|
62
136
|
async (req, res) => {
|
|
63
137
|
try {
|
|
@@ -68,11 +142,24 @@ const createGmailPushHandler = ({
|
|
|
68
142
|
}
|
|
69
143
|
|
|
70
144
|
const bodyBuffer = extractBodyBuffer(req.body);
|
|
71
|
-
const { payload } = parsePushEnvelope(bodyBuffer);
|
|
145
|
+
const { envelope, payload } = parsePushEnvelope(bodyBuffer);
|
|
72
146
|
const email = String(payload?.emailAddress || "").trim().toLowerCase();
|
|
73
147
|
if (!email) {
|
|
74
148
|
return res.status(200).json({ ok: true, ignored: true, reason: "missing_email" });
|
|
75
149
|
}
|
|
150
|
+
if (
|
|
151
|
+
!shouldProcessPushEvent({
|
|
152
|
+
envelope,
|
|
153
|
+
payload,
|
|
154
|
+
receivedAt: Date.now(),
|
|
155
|
+
})
|
|
156
|
+
) {
|
|
157
|
+
return res.status(200).json({
|
|
158
|
+
ok: true,
|
|
159
|
+
ignored: true,
|
|
160
|
+
reason: "duplicate_event",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
76
163
|
|
|
77
164
|
const target = resolveTargetByEmail?.(email);
|
|
78
165
|
if (!target?.port) {
|
|
@@ -80,15 +167,22 @@ const createGmailPushHandler = ({
|
|
|
80
167
|
}
|
|
81
168
|
|
|
82
169
|
try {
|
|
83
|
-
const proxied = await
|
|
170
|
+
const proxied = await proxyPushToServeImpl({
|
|
84
171
|
port: target.port,
|
|
85
172
|
bodyBuffer,
|
|
86
173
|
headers: req.headers || {},
|
|
87
174
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
175
|
+
if (isSuccessfulProxyStatus(proxied.statusCode)) {
|
|
176
|
+
shouldProcessPushEvent.markProcessed?.({
|
|
177
|
+
envelope,
|
|
178
|
+
payload,
|
|
179
|
+
receivedAt: Date.now(),
|
|
180
|
+
});
|
|
181
|
+
await markPushReceived?.({
|
|
182
|
+
accountId: target.accountId,
|
|
183
|
+
at: Date.now(),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
92
186
|
return res
|
|
93
187
|
.status(proxied.statusCode)
|
|
94
188
|
.send(proxied.body || "");
|
|
@@ -106,4 +200,6 @@ const createGmailPushHandler = ({
|
|
|
106
200
|
|
|
107
201
|
module.exports = {
|
|
108
202
|
createGmailPushHandler,
|
|
203
|
+
createGmailPushEventDeduper,
|
|
204
|
+
createPushEventDedupeKey,
|
|
109
205
|
};
|
|
@@ -14,29 +14,14 @@ const {
|
|
|
14
14
|
allocateServePort,
|
|
15
15
|
} = require("./google-state");
|
|
16
16
|
const { createGmailServeManager } = require("./gmail-serve");
|
|
17
|
+
const { parseJsonObjectFromNoisyOutput, parseJsonSafe } = require("./utils/json");
|
|
17
18
|
const { createWebhook } = require("./webhooks");
|
|
18
|
-
|
|
19
|
-
const quoteShellArg = (value) =>
|
|
20
|
-
`"${String(value || "").replace(/(["\\$`])/g, "\\$1")}"`;
|
|
21
|
-
|
|
22
|
-
const parseJsonMaybe = (raw) => {
|
|
23
|
-
const text = String(raw || "").trim();
|
|
24
|
-
if (!text) return null;
|
|
25
|
-
try {
|
|
26
|
-
return JSON.parse(text);
|
|
27
|
-
} catch {}
|
|
28
|
-
const firstBrace = text.indexOf("{");
|
|
29
|
-
const lastBrace = text.lastIndexOf("}");
|
|
30
|
-
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
31
|
-
try {
|
|
32
|
-
return JSON.parse(text.slice(firstBrace, lastBrace + 1));
|
|
33
|
-
} catch {}
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
36
|
-
};
|
|
19
|
+
const { quoteShellArg } = require("./utils/shell");
|
|
37
20
|
|
|
38
21
|
const parseExpirationFromOutput = (raw) => {
|
|
39
|
-
const parsed =
|
|
22
|
+
const parsed =
|
|
23
|
+
parseJsonSafe(raw, null, { trim: true }) ||
|
|
24
|
+
parseJsonObjectFromNoisyOutput(raw);
|
|
40
25
|
if (parsed?.expiration) {
|
|
41
26
|
const numeric = Number.parseInt(String(parsed.expiration), 10);
|
|
42
27
|
if (Number.isFinite(numeric) && numeric > 0) return numeric;
|
package/lib/server/helpers.js
CHANGED
|
@@ -5,6 +5,9 @@ const {
|
|
|
5
5
|
kOnboardingModelProviders,
|
|
6
6
|
gogClientCredentialsPath,
|
|
7
7
|
} = require("./constants");
|
|
8
|
+
const { isTruthyFlag } = require("./utils/boolean");
|
|
9
|
+
const { parseJsonObjectFromNoisyOutput } = require("./utils/json");
|
|
10
|
+
const { normalizeIp } = require("./utils/network");
|
|
8
11
|
|
|
9
12
|
const normalizeOpenclawVersion = (rawVersion) => {
|
|
10
13
|
if (!rawVersion) return null;
|
|
@@ -32,19 +35,7 @@ const compareVersionParts = (a, b) => {
|
|
|
32
35
|
return 0;
|
|
33
36
|
};
|
|
34
37
|
|
|
35
|
-
const parseJsonFromNoisyOutput = (raw) =>
|
|
36
|
-
const text = String(raw || "");
|
|
37
|
-
const firstBrace = text.indexOf("{");
|
|
38
|
-
const lastBrace = text.lastIndexOf("}");
|
|
39
|
-
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
return JSON.parse(text.slice(firstBrace, lastBrace + 1));
|
|
44
|
-
} catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
38
|
+
const parseJsonFromNoisyOutput = (raw) => parseJsonObjectFromNoisyOutput(raw);
|
|
48
39
|
|
|
49
40
|
const parseJwtPayload = (token) => {
|
|
50
41
|
try {
|
|
@@ -63,14 +54,7 @@ const getCodexAccountId = (accessToken) => {
|
|
|
63
54
|
return typeof accountId === "string" && accountId ? accountId : null;
|
|
64
55
|
};
|
|
65
56
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
const isTruthyEnvFlag = (value) =>
|
|
69
|
-
["1", "true", "yes", "on"].includes(
|
|
70
|
-
String(value || "")
|
|
71
|
-
.trim()
|
|
72
|
-
.toLowerCase(),
|
|
73
|
-
);
|
|
57
|
+
const isTruthyEnvFlag = (value) => isTruthyFlag(value);
|
|
74
58
|
const isDebugEnabled = () =>
|
|
75
59
|
isTruthyEnvFlag(process.env.ALPHACLAW_DEBUG) ||
|
|
76
60
|
isTruthyEnvFlag(process.env.DEBUG);
|
|
@@ -12,16 +12,8 @@ const {
|
|
|
12
12
|
} = require("../google-state");
|
|
13
13
|
const { syncBootstrapPromptFiles } = require("../onboarding/workspace");
|
|
14
14
|
const { installGogCliSkill } = require("../gog-skill");
|
|
15
|
-
|
|
16
|
-
const quoteShellArg = (
|
|
17
|
-
|
|
18
|
-
const parseJsonSafe = (raw, fallbackValue) => {
|
|
19
|
-
try {
|
|
20
|
-
return JSON.parse(String(raw || ""));
|
|
21
|
-
} catch {
|
|
22
|
-
return fallbackValue;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
15
|
+
const { parseJsonSafe } = require("../utils/json");
|
|
16
|
+
const { quoteShellArg } = require("../utils/shell");
|
|
25
17
|
|
|
26
18
|
const uniqueServiceLabels = (scopes) =>
|
|
27
19
|
Array.from(
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const { OPENCLAW_DIR } = require("../constants");
|
|
3
3
|
const { isDebugEnabled } = require("../helpers");
|
|
4
|
+
const { parseBooleanValue } = require("../utils/boolean");
|
|
5
|
+
const { quoteShellArg } = require("../utils/shell");
|
|
4
6
|
const topicRegistry = require("../topic-registry");
|
|
5
7
|
const { syncConfigForTelegram } = require("../telegram-workspace");
|
|
6
|
-
|
|
7
|
-
const parseBooleanValue = (value, fallbackValue = false) => {
|
|
8
|
-
if (typeof value === "boolean") return value;
|
|
9
|
-
if (typeof value === "number") return value !== 0;
|
|
10
|
-
if (typeof value === "string") {
|
|
11
|
-
const normalized = value.trim().toLowerCase();
|
|
12
|
-
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
|
13
|
-
if (["false", "0", "no", "off", ""].includes(normalized)) return false;
|
|
14
|
-
}
|
|
15
|
-
return fallbackValue;
|
|
16
|
-
};
|
|
17
8
|
const resolveGroupId = (req) => {
|
|
18
9
|
const body = req.body || {};
|
|
19
10
|
const rawGroupId = body.groupId ?? body.chatId;
|
|
@@ -53,13 +44,11 @@ const normalizeGitSyncMessagePart = (value) =>
|
|
|
53
44
|
.replace(/\s+/g, " ")
|
|
54
45
|
.trim();
|
|
55
46
|
|
|
56
|
-
const quoteShellArg = (value) => `'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
|
|
57
|
-
|
|
58
47
|
const buildTelegramGitSyncCommand = (action, target = "") => {
|
|
59
48
|
const safeAction = normalizeGitSyncMessagePart(action);
|
|
60
49
|
const safeTarget = normalizeGitSyncMessagePart(target);
|
|
61
50
|
const message = `telegram workspace: ${safeAction} ${safeTarget}`.trim();
|
|
62
|
-
return `alphaclaw git-sync -m ${quoteShellArg(message)}`;
|
|
51
|
+
return `alphaclaw git-sync -m ${quoteShellArg(message, { strategy: "single" })}`;
|
|
63
52
|
};
|
|
64
53
|
|
|
65
54
|
const registerTelegramRoutes = ({
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
const topicRegistry = require("../topic-registry");
|
|
2
|
+
const { parsePositiveInt } = require("../utils/number");
|
|
2
3
|
|
|
3
4
|
const kSummaryCacheTtlMs = 60 * 1000;
|
|
4
5
|
const kClientTimeZoneHeader = "x-client-timezone";
|
|
5
6
|
|
|
6
|
-
const parsePositiveInt = (value, fallbackValue) => {
|
|
7
|
-
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
8
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackValue;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
7
|
const createSummaryCache = () => new Map();
|
|
12
8
|
const toTitleLabel = (value) => {
|
|
13
9
|
const raw = String(value || "").trim();
|
|
@@ -5,15 +5,11 @@ const {
|
|
|
5
5
|
deleteWebhook,
|
|
6
6
|
validateWebhookName,
|
|
7
7
|
} = require("../webhooks");
|
|
8
|
+
const { isTruthyFlag } = require("../utils/boolean");
|
|
8
9
|
|
|
9
10
|
const isFiniteInteger = (value) =>
|
|
10
11
|
Number.isFinite(value) && Number.isInteger(value);
|
|
11
|
-
const parseBooleanFlag = (value) =>
|
|
12
|
-
const normalized = String(value == null ? "" : value)
|
|
13
|
-
.trim()
|
|
14
|
-
.toLowerCase();
|
|
15
|
-
return ["1", "true", "yes", "on"].includes(normalized);
|
|
16
|
-
};
|
|
12
|
+
const parseBooleanFlag = (value) => isTruthyFlag(value);
|
|
17
13
|
|
|
18
14
|
const buildHealth = ({ totalCount, errorCount }) => {
|
|
19
15
|
if (!totalCount || totalCount <= 0) return "green";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const isTruthyFlag = (value) =>
|
|
2
|
+
["1", "true", "yes", "on"].includes(
|
|
3
|
+
String(value ?? "")
|
|
4
|
+
.trim()
|
|
5
|
+
.toLowerCase(),
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
const parseBooleanValue = (value, fallbackValue = false) => {
|
|
9
|
+
if (typeof value === "boolean") return value;
|
|
10
|
+
if (typeof value === "number") return value !== 0;
|
|
11
|
+
if (typeof value === "string") {
|
|
12
|
+
const normalized = value.trim().toLowerCase();
|
|
13
|
+
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
|
14
|
+
if (["false", "0", "no", "off", ""].includes(normalized)) return false;
|
|
15
|
+
}
|
|
16
|
+
return fallbackValue;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
isTruthyFlag,
|
|
21
|
+
parseBooleanValue,
|
|
22
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const parseJsonSafe = (rawValue, fallbackValue = null, options = {}) => {
|
|
2
|
+
const shouldTrim = options?.trim === true;
|
|
3
|
+
const text = shouldTrim
|
|
4
|
+
? String(rawValue ?? "").trim()
|
|
5
|
+
: String(rawValue ?? "");
|
|
6
|
+
if (!text) return fallbackValue;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(text);
|
|
9
|
+
} catch {
|
|
10
|
+
return fallbackValue;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const parseJsonObjectFromNoisyOutput = (rawValue) => {
|
|
15
|
+
const text = String(rawValue ?? "");
|
|
16
|
+
const firstBrace = text.indexOf("{");
|
|
17
|
+
const lastBrace = text.lastIndexOf("}");
|
|
18
|
+
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(text.slice(firstBrace, lastBrace + 1));
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
parseJsonSafe,
|
|
30
|
+
parseJsonObjectFromNoisyOutput,
|
|
31
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const quoteShellArg = (value, options = {}) => {
|
|
2
|
+
const strategy = String(options?.strategy || "double").trim().toLowerCase();
|
|
3
|
+
const normalizedValue = String(value || "");
|
|
4
|
+
|
|
5
|
+
if (strategy === "single") {
|
|
6
|
+
return `'${normalizedValue.replace(/'/g, `'\"'\"'`)}'`;
|
|
7
|
+
}
|
|
8
|
+
if (strategy === "double") {
|
|
9
|
+
return `"${normalizedValue.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
10
|
+
}
|
|
11
|
+
throw new Error(`Unsupported shell quote strategy: ${strategy}`);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
quoteShellArg,
|
|
16
|
+
};
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
const http = require("http");
|
|
2
2
|
const https = require("https");
|
|
3
3
|
const { URL } = require("url");
|
|
4
|
+
const { normalizeIp } = require("./utils/network");
|
|
4
5
|
|
|
5
6
|
const kRedactedHeaderKeys = new Set(["authorization", "cookie", "x-webhook-token"]);
|
|
6
7
|
const kGmailDedupeTtlMs = 24 * 60 * 60 * 1000;
|
|
7
8
|
const kGmailDedupeCleanupIntervalMs = 60 * 1000;
|
|
8
9
|
|
|
9
|
-
const normalizeIp = (ip) => String(ip || "").replace(/^::ffff:/, "");
|
|
10
|
-
|
|
11
10
|
const sanitizeHeaders = (headers) => {
|
|
12
11
|
const sanitized = {};
|
|
13
12
|
for (const [key, value] of Object.entries(headers || {})) {
|