@chrysb/alphaclaw 0.4.4 → 0.4.6-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/README.md +21 -18
- package/lib/public/css/theme.css +29 -0
- package/lib/public/js/app.js +41 -2
- package/lib/public/js/components/badge.js +4 -0
- package/lib/public/js/components/doctor/findings-list.js +191 -0
- package/lib/public/js/components/doctor/fix-card-modal.js +144 -0
- package/lib/public/js/components/doctor/general-warning.js +37 -0
- package/lib/public/js/components/doctor/helpers.js +169 -0
- package/lib/public/js/components/doctor/index.js +536 -0
- package/lib/public/js/components/doctor/summary-cards.js +24 -0
- package/lib/public/js/lib/api.js +79 -0
- package/lib/server/commands.js +8 -4
- package/lib/server/constants.js +22 -26
- package/lib/server/db/doctor/index.js +529 -0
- package/lib/server/db/doctor/schema.js +69 -0
- package/lib/server/doctor/constants.js +43 -0
- package/lib/server/doctor/normalize.js +214 -0
- package/lib/server/doctor/prompt.js +89 -0
- package/lib/server/doctor/service.js +392 -0
- package/lib/server/doctor/workspace-fingerprint.js +126 -0
- 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/doctor.js +123 -0
- package/lib/server/routes/google.js +2 -10
- package/lib/server/routes/system.js +7 -1
- 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 +77 -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/lib/server.js +42 -0
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { buildDoctorPriorityCounts } from "./helpers.js";
|
|
4
|
+
|
|
5
|
+
const html = htm.bind(h);
|
|
6
|
+
|
|
7
|
+
const SummaryCard = ({ title = "", value = 0, toneClassName = "" }) => html`
|
|
8
|
+
<div class="bg-surface border border-border rounded-xl p-4">
|
|
9
|
+
<h3 class="card-label text-xs">${title}</h3>
|
|
10
|
+
<div class=${`text-lg font-semibold mt-1 ${toneClassName}`}>${value}</div>
|
|
11
|
+
</div>
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export const DoctorSummaryCards = ({ cards = [] }) => {
|
|
15
|
+
const counts = buildDoctorPriorityCounts(cards);
|
|
16
|
+
return html`
|
|
17
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-3">
|
|
18
|
+
<${SummaryCard} title="Open Findings" value=${cards.length} />
|
|
19
|
+
<${SummaryCard} title="P0" value=${counts.P0} toneClassName="text-red-400" />
|
|
20
|
+
<${SummaryCard} title="P1" value=${counts.P1} toneClassName="text-yellow-400" />
|
|
21
|
+
<${SummaryCard} title="P2" value=${counts.P2} toneClassName="text-gray-300" />
|
|
22
|
+
</div>
|
|
23
|
+
`;
|
|
24
|
+
};
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -198,6 +198,85 @@ export const fetchAgentSessions = async () => {
|
|
|
198
198
|
return parseJsonOrThrow(res, "Could not load agent sessions");
|
|
199
199
|
};
|
|
200
200
|
|
|
201
|
+
export const fetchDoctorStatus = async () => {
|
|
202
|
+
const res = await authFetch("/api/doctor/status");
|
|
203
|
+
return parseJsonOrThrow(res, "Could not load Doctor status");
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const startDoctorRun = async () => {
|
|
207
|
+
const res = await authFetch("/api/doctor/run", {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: { "Content-Type": "application/json" },
|
|
210
|
+
body: JSON.stringify({}),
|
|
211
|
+
});
|
|
212
|
+
return parseJsonOrThrow(res, "Could not start Doctor run");
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export const importDoctorResult = async (rawOutput = "") => {
|
|
216
|
+
const res = await authFetch("/api/doctor/import", {
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers: { "Content-Type": "application/json" },
|
|
219
|
+
body: JSON.stringify({ rawOutput: String(rawOutput || "") }),
|
|
220
|
+
});
|
|
221
|
+
return parseJsonOrThrow(res, "Could not import Doctor result");
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const fetchDoctorRuns = async (limit = 10) => {
|
|
225
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
226
|
+
const res = await authFetch(`/api/doctor/runs?${params.toString()}`);
|
|
227
|
+
return parseJsonOrThrow(res, "Could not load Doctor runs");
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const fetchDoctorCards = async ({ runId = "all" } = {}) => {
|
|
231
|
+
const params = new URLSearchParams();
|
|
232
|
+
if (String(runId || "").trim()) params.set("runId", String(runId || ""));
|
|
233
|
+
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
234
|
+
const res = await authFetch(`/api/doctor/cards${suffix}`);
|
|
235
|
+
return parseJsonOrThrow(res, "Could not load Doctor findings");
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export const fetchDoctorRun = async (runId) => {
|
|
239
|
+
const res = await authFetch(`/api/doctor/runs/${encodeURIComponent(String(runId || ""))}`);
|
|
240
|
+
return parseJsonOrThrow(res, "Could not load Doctor run");
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export const fetchDoctorRunCards = async (runId) => {
|
|
244
|
+
const res = await authFetch(
|
|
245
|
+
`/api/doctor/runs/${encodeURIComponent(String(runId || ""))}/cards`,
|
|
246
|
+
);
|
|
247
|
+
return parseJsonOrThrow(res, "Could not load Doctor cards");
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const updateDoctorCardStatus = async ({ cardId, status }) => {
|
|
251
|
+
const res = await authFetch(`/api/doctor/cards/${encodeURIComponent(String(cardId || ""))}/status`, {
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers: { "Content-Type": "application/json" },
|
|
254
|
+
body: JSON.stringify({ status: String(status || "") }),
|
|
255
|
+
});
|
|
256
|
+
return parseJsonOrThrow(res, "Could not update Doctor card status");
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const sendDoctorCardFix = async ({
|
|
260
|
+
cardId,
|
|
261
|
+
sessionId = "",
|
|
262
|
+
replyChannel = "",
|
|
263
|
+
replyTo = "",
|
|
264
|
+
} = {}) => {
|
|
265
|
+
const res = await authFetch(
|
|
266
|
+
`/api/doctor/findings/${encodeURIComponent(String(cardId || ""))}/fix`,
|
|
267
|
+
{
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: { "Content-Type": "application/json" },
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
sessionId: String(sessionId || ""),
|
|
272
|
+
replyChannel: String(replyChannel || ""),
|
|
273
|
+
replyTo: String(replyTo || ""),
|
|
274
|
+
}),
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
return parseJsonOrThrow(res, "Could not send Doctor fix request");
|
|
278
|
+
};
|
|
279
|
+
|
|
201
280
|
export const sendAgentMessage = async ({
|
|
202
281
|
message = "",
|
|
203
282
|
sessionKey = "",
|
package/lib/server/commands.js
CHANGED
|
@@ -4,7 +4,11 @@ const { OPENCLAW_DIR, GOG_KEYRING_PASSWORD } = require("./constants");
|
|
|
4
4
|
const createCommands = ({ gatewayEnv }) => {
|
|
5
5
|
const shellCmd = (cmd, opts = {}) =>
|
|
6
6
|
new Promise((resolve, reject) => {
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
logStdout,
|
|
9
|
+
timeoutMs = 60000,
|
|
10
|
+
...execOpts
|
|
11
|
+
} = opts;
|
|
8
12
|
const shouldLogStdout =
|
|
9
13
|
typeof logStdout === "boolean" ? logStdout : !cmd.includes("--json");
|
|
10
14
|
console.log(
|
|
@@ -13,7 +17,7 @@ const createCommands = ({ gatewayEnv }) => {
|
|
|
13
17
|
.replace(/sk-[^\s"]+/g, "***")
|
|
14
18
|
.slice(0, 200)}`,
|
|
15
19
|
);
|
|
16
|
-
exec(cmd, { timeout:
|
|
20
|
+
exec(cmd, { timeout: timeoutMs, ...execOpts }, (err, stdout, stderr) => {
|
|
17
21
|
if (err) {
|
|
18
22
|
console.error(
|
|
19
23
|
`[onboard] Error: ${(stderr || err.message).slice(0, 300)}`,
|
|
@@ -27,14 +31,14 @@ const createCommands = ({ gatewayEnv }) => {
|
|
|
27
31
|
});
|
|
28
32
|
});
|
|
29
33
|
|
|
30
|
-
const clawCmd = (cmd, { quiet = false } = {}) =>
|
|
34
|
+
const clawCmd = (cmd, { quiet = false, timeoutMs = 15000 } = {}) =>
|
|
31
35
|
new Promise((resolve) => {
|
|
32
36
|
if (!quiet) console.log(`[alphaclaw] Running: openclaw ${cmd}`);
|
|
33
37
|
exec(
|
|
34
38
|
`openclaw ${cmd}`,
|
|
35
39
|
{
|
|
36
40
|
env: gatewayEnv(),
|
|
37
|
-
timeout:
|
|
41
|
+
timeout: timeoutMs,
|
|
38
42
|
},
|
|
39
43
|
(err, stdout, stderr) => {
|
|
40
44
|
const result = {
|
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
|
);
|