@p697/clawket 0.4.5 → 0.5.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/LICENSE +661 -0
- package/dist/index.js +707 -6
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1133,10 +1133,11 @@ function resolveGatewayUrl(explicitUrl) {
|
|
|
1133
1133
|
}
|
|
1134
1134
|
function readOpenClawInfo() {
|
|
1135
1135
|
const openclaw = resolveOpenClawPaths();
|
|
1136
|
+
const envGatewayPort = readGatewayPortEnv();
|
|
1136
1137
|
if (!existsSync3(openclaw.configPath)) {
|
|
1137
1138
|
return {
|
|
1138
1139
|
configFound: false,
|
|
1139
|
-
gatewayPort:
|
|
1140
|
+
gatewayPort: envGatewayPort,
|
|
1140
1141
|
authMode: null,
|
|
1141
1142
|
token: readGatewayTokenEnv(),
|
|
1142
1143
|
password: readGatewayPasswordEnv()
|
|
@@ -1145,13 +1146,13 @@ function readOpenClawInfo() {
|
|
|
1145
1146
|
try {
|
|
1146
1147
|
const parsed = JSON.parse(readFileSync3(openclaw.configPath, "utf8"));
|
|
1147
1148
|
const rawPort = parsed.gateway?.port;
|
|
1148
|
-
const
|
|
1149
|
+
const configuredGatewayPort = typeof rawPort === "number" && Number.isInteger(rawPort) ? rawPort : null;
|
|
1149
1150
|
const authMode = parsed.gateway?.auth?.mode === "token" || parsed.gateway?.auth?.mode === "password" ? parsed.gateway.auth.mode : null;
|
|
1150
1151
|
const token = readConfiguredSecret(parsed.gateway?.auth?.token) ?? readGatewayTokenEnv();
|
|
1151
1152
|
const password = readConfiguredSecret(parsed.gateway?.auth?.password) ?? readGatewayPasswordEnv();
|
|
1152
1153
|
return {
|
|
1153
1154
|
configFound: true,
|
|
1154
|
-
gatewayPort,
|
|
1155
|
+
gatewayPort: envGatewayPort ?? configuredGatewayPort,
|
|
1155
1156
|
authMode,
|
|
1156
1157
|
token,
|
|
1157
1158
|
password
|
|
@@ -1159,7 +1160,7 @@ function readOpenClawInfo() {
|
|
|
1159
1160
|
} catch {
|
|
1160
1161
|
return {
|
|
1161
1162
|
configFound: true,
|
|
1162
|
-
gatewayPort:
|
|
1163
|
+
gatewayPort: envGatewayPort,
|
|
1163
1164
|
authMode: null,
|
|
1164
1165
|
token: readGatewayTokenEnv(),
|
|
1165
1166
|
password: readGatewayPasswordEnv()
|
|
@@ -1233,6 +1234,337 @@ async function configureOpenClawLanAccess(params) {
|
|
|
1233
1234
|
controlUiOrigin
|
|
1234
1235
|
};
|
|
1235
1236
|
}
|
|
1237
|
+
function stripAnsi(text) {
|
|
1238
|
+
return text.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
1239
|
+
}
|
|
1240
|
+
function collectCliOutput(parts) {
|
|
1241
|
+
return parts.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => stripAnsi(value.trim())).join("\n").trim();
|
|
1242
|
+
}
|
|
1243
|
+
function isUnsupportedJsonOptionOutput(text) {
|
|
1244
|
+
return /unknown option ['"]?--json['"]?/i.test(text);
|
|
1245
|
+
}
|
|
1246
|
+
async function readOpenClawPermissions() {
|
|
1247
|
+
const openclaw = resolveOpenClawPaths();
|
|
1248
|
+
const config = await readJsonFile(openclaw.configPath);
|
|
1249
|
+
const approvalsPath = resolveOpenClawExecApprovalsPath();
|
|
1250
|
+
const approvals = await readJsonFile(approvalsPath);
|
|
1251
|
+
const configEnv = readConfigEnvVars(config);
|
|
1252
|
+
const tools = readRecord(config?.tools);
|
|
1253
|
+
const currentAgent = resolveCurrentAgent(config);
|
|
1254
|
+
const agentTools = readRecord(currentAgent.record?.tools);
|
|
1255
|
+
const web = readRecord(tools?.web);
|
|
1256
|
+
const webSearch = readRecord(web?.search);
|
|
1257
|
+
const webFetch = readRecord(web?.fetch);
|
|
1258
|
+
const exec = resolveMergedExecConfig(tools, agentTools);
|
|
1259
|
+
const toolProfile = resolveToolProfile(tools, agentTools);
|
|
1260
|
+
const toolDeny = uniqueSortedStrings([
|
|
1261
|
+
...readStringArray(tools?.deny),
|
|
1262
|
+
...readStringArray(agentTools?.deny)
|
|
1263
|
+
]);
|
|
1264
|
+
const searchEnabled = readBoolean(webSearch?.enabled, true);
|
|
1265
|
+
const searchProvider = readString(webSearch?.provider) ?? "auto";
|
|
1266
|
+
const searchConfigured = resolveWebSearchConfigured(webSearch, searchProvider, configEnv);
|
|
1267
|
+
const webReasons = [];
|
|
1268
|
+
let webStatus;
|
|
1269
|
+
let webSummary;
|
|
1270
|
+
if (toolDeny.includes("web_search") && toolDeny.includes("web_fetch")) {
|
|
1271
|
+
webStatus = "disabled";
|
|
1272
|
+
webSummary = "Web search and fetch are blocked by tool policy.";
|
|
1273
|
+
webReasons.push("Global tool policy denies both web_search and web_fetch.");
|
|
1274
|
+
} else if (!searchEnabled && !readBoolean(webFetch?.enabled, true)) {
|
|
1275
|
+
webStatus = "disabled";
|
|
1276
|
+
webSummary = "Web search and fetch are turned off.";
|
|
1277
|
+
webReasons.push("tools.web.search.enabled is false.");
|
|
1278
|
+
webReasons.push("tools.web.fetch.enabled is false.");
|
|
1279
|
+
} else if (searchEnabled && !searchConfigured) {
|
|
1280
|
+
webStatus = "configuration_needed";
|
|
1281
|
+
webSummary = "Web search is enabled, but no search provider key was found.";
|
|
1282
|
+
webReasons.push(searchProvider === "auto" ? "No supported web search provider API key was found in config or current environment." : `Provider "${searchProvider}" is selected, but its API key was not found in config or current environment.`);
|
|
1283
|
+
} else {
|
|
1284
|
+
webStatus = "available";
|
|
1285
|
+
webSummary = "Common web tools look available.";
|
|
1286
|
+
if (toolDeny.includes("web_search")) {
|
|
1287
|
+
webStatus = "restricted";
|
|
1288
|
+
webSummary = "Web fetch is available, but web search is blocked by tool policy.";
|
|
1289
|
+
webReasons.push("Global tool policy denies web_search.");
|
|
1290
|
+
} else if (toolDeny.includes("web_fetch")) {
|
|
1291
|
+
webStatus = "restricted";
|
|
1292
|
+
webSummary = "Web search is available, but web fetch is blocked by tool policy.";
|
|
1293
|
+
webReasons.push("Global tool policy denies web_fetch.");
|
|
1294
|
+
} else {
|
|
1295
|
+
if (!searchEnabled) {
|
|
1296
|
+
webReasons.push("tools.web.search.enabled is false.");
|
|
1297
|
+
}
|
|
1298
|
+
if (!readBoolean(webFetch?.enabled, true)) {
|
|
1299
|
+
webReasons.push("tools.web.fetch.enabled is false.");
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const firecrawlConfigured = resolveFirecrawlConfigured(webFetch, configEnv);
|
|
1304
|
+
const configuredHost = readExecHost(exec?.host);
|
|
1305
|
+
const sandboxMode = readSandboxMode(config, currentAgent.id);
|
|
1306
|
+
const implicitSandboxFallback = configuredHost === "sandbox" && sandboxMode === "off";
|
|
1307
|
+
const effectiveHost = implicitSandboxFallback ? "gateway" : configuredHost;
|
|
1308
|
+
const configSecurity = readExecSecurity(exec?.security, configuredHost === "sandbox" ? "deny" : "allowlist");
|
|
1309
|
+
const configAsk = readExecAsk(exec?.ask, "on-miss");
|
|
1310
|
+
const safeBins = readStringArray(exec?.safeBins);
|
|
1311
|
+
const safeBinTrustedDirs = uniqueSortedStrings([
|
|
1312
|
+
"/bin",
|
|
1313
|
+
"/usr/bin",
|
|
1314
|
+
...readStringArray(exec?.safeBinTrustedDirs)
|
|
1315
|
+
]);
|
|
1316
|
+
const trustedDirWarnings = safeBinTrustedDirs.filter((dir) => ["/opt/homebrew/bin", "/usr/local/bin", "/opt/local/bin", "/snap/bin"].includes(dir)).map((dir) => `safe-bin trust includes ${dir}; this is often needed, but commands there are treated as explicitly trusted.`);
|
|
1317
|
+
const resolvedApprovals = resolveExecApprovalsSummary(approvals, {
|
|
1318
|
+
security: configSecurity,
|
|
1319
|
+
ask: configAsk
|
|
1320
|
+
});
|
|
1321
|
+
const usesHostApprovals = configuredHost === "gateway" || configuredHost === "node";
|
|
1322
|
+
const effectiveSecurity = usesHostApprovals ? minExecSecurity(configSecurity, resolvedApprovals.security) : configSecurity;
|
|
1323
|
+
const effectiveAsk = usesHostApprovals ? resolvedApprovals.ask === "off" ? "off" : maxExecAsk(configAsk, resolvedApprovals.ask) : "off";
|
|
1324
|
+
const toolPolicyDenied = deniesExecTool(toolDeny);
|
|
1325
|
+
const execToolAvailable = toolProfileAllowsExec(toolProfile) && !toolPolicyDenied;
|
|
1326
|
+
const execReasons = [];
|
|
1327
|
+
let execStatus;
|
|
1328
|
+
let execSummary;
|
|
1329
|
+
if (!execToolAvailable) {
|
|
1330
|
+
execStatus = "disabled";
|
|
1331
|
+
execSummary = "This agent cannot run commands right now.";
|
|
1332
|
+
if (!toolProfileAllowsExec(toolProfile)) {
|
|
1333
|
+
execReasons.push(`The current agent uses the ${toolProfile} tool profile, which does not expose command tools.`);
|
|
1334
|
+
}
|
|
1335
|
+
if (toolPolicyDenied) {
|
|
1336
|
+
execReasons.push("A tool deny rule is blocking command execution for the current agent.");
|
|
1337
|
+
}
|
|
1338
|
+
} else if (implicitSandboxFallback) {
|
|
1339
|
+
execStatus = "available";
|
|
1340
|
+
execSummary = "Commands currently run directly on this OpenClaw machine.";
|
|
1341
|
+
execReasons.push("The current agent still exposes command tools.");
|
|
1342
|
+
execReasons.push("Sandbox mode is off, so commands are not running in an isolated sandbox.");
|
|
1343
|
+
execReasons.push("OpenClaw is currently falling back to direct host execution on this machine.");
|
|
1344
|
+
} else if (effectiveHost === "sandbox") {
|
|
1345
|
+
execStatus = "available";
|
|
1346
|
+
execSummary = "Commands run inside OpenClaw's sandbox.";
|
|
1347
|
+
execReasons.push("The current agent still exposes command tools.");
|
|
1348
|
+
execReasons.push(`Sandbox mode is ${sandboxMode}.`);
|
|
1349
|
+
} else if (effectiveSecurity === "deny") {
|
|
1350
|
+
execStatus = "disabled";
|
|
1351
|
+
execSummary = "Command execution is disabled.";
|
|
1352
|
+
execReasons.push(`Effective exec security resolves to ${effectiveSecurity}.`);
|
|
1353
|
+
} else if (effectiveAsk !== "off") {
|
|
1354
|
+
execStatus = "needs_approval";
|
|
1355
|
+
execSummary = "Commands can run, but exec approvals currently require confirmation.";
|
|
1356
|
+
execReasons.push(`Effective exec ask policy is ${effectiveAsk}.`);
|
|
1357
|
+
if (effectiveSecurity === "allowlist" && resolvedApprovals.allowlistCount === 0 && safeBins.length === 0) {
|
|
1358
|
+
execReasons.push("No allowlist entries or safe bins are configured yet, so most commands will still be denied.");
|
|
1359
|
+
}
|
|
1360
|
+
} else if (effectiveSecurity === "allowlist") {
|
|
1361
|
+
execStatus = "restricted";
|
|
1362
|
+
execSummary = "Commands are limited to allowlisted executables and safe bins.";
|
|
1363
|
+
execReasons.push("Effective exec security is allowlist.");
|
|
1364
|
+
if (resolvedApprovals.allowlistCount === 0 && safeBins.length === 0) {
|
|
1365
|
+
execReasons.push("No allowlist entries or safe bins are configured yet.");
|
|
1366
|
+
}
|
|
1367
|
+
} else {
|
|
1368
|
+
execStatus = "available";
|
|
1369
|
+
execSummary = "Command execution is broadly available.";
|
|
1370
|
+
}
|
|
1371
|
+
if (usesHostApprovals && resolvedApprovals.security !== configSecurity) {
|
|
1372
|
+
execReasons.push(`exec-approvals.json is stricter than tools.exec.security (${configSecurity} -> ${resolvedApprovals.security}).`);
|
|
1373
|
+
}
|
|
1374
|
+
if (usesHostApprovals && resolvedApprovals.ask !== configAsk) {
|
|
1375
|
+
execReasons.push(`exec-approvals.json is stricter than tools.exec.ask (${configAsk} -> ${resolvedApprovals.ask}).`);
|
|
1376
|
+
}
|
|
1377
|
+
if (usesHostApprovals && safeBins.some(isInterpreterLikeSafeBin)) {
|
|
1378
|
+
execStatus = execStatus === "disabled" ? execStatus : "restricted";
|
|
1379
|
+
execReasons.push("Interpreter/runtime binaries appear in safeBins and may still be unsafe or blocked.");
|
|
1380
|
+
}
|
|
1381
|
+
if (usesHostApprovals) {
|
|
1382
|
+
execReasons.push(...trustedDirWarnings);
|
|
1383
|
+
}
|
|
1384
|
+
if (!usesHostApprovals) {
|
|
1385
|
+
execReasons.push(`The app controls tools.exec.security=${configSecurity} and tools.exec.ask=${configAsk}, but this current command path is not using OpenClaw's host approval flow.`);
|
|
1386
|
+
}
|
|
1387
|
+
const codeReasons = [];
|
|
1388
|
+
let codeStatus = execStatus;
|
|
1389
|
+
let codeSummary = execSummary;
|
|
1390
|
+
if (!execToolAvailable) {
|
|
1391
|
+
codeStatus = "disabled";
|
|
1392
|
+
codeSummary = "Scripts are currently unavailable because this agent cannot run commands.";
|
|
1393
|
+
} else if (implicitSandboxFallback) {
|
|
1394
|
+
codeStatus = "available";
|
|
1395
|
+
codeSummary = "Scripts follow the same direct command path as command execution on this machine.";
|
|
1396
|
+
codeReasons.push("Code execution follows the same direct command path as command execution.");
|
|
1397
|
+
} else if (effectiveHost === "sandbox") {
|
|
1398
|
+
codeStatus = "available";
|
|
1399
|
+
codeSummary = "Code runs inside OpenClaw's sandbox.";
|
|
1400
|
+
codeReasons.push("Code execution follows the same sandboxed path as command execution.");
|
|
1401
|
+
} else if (effectiveSecurity === "deny") {
|
|
1402
|
+
codeStatus = "disabled";
|
|
1403
|
+
codeSummary = "Code execution is disabled because command execution is disabled.";
|
|
1404
|
+
} else if (effectiveAsk !== "off") {
|
|
1405
|
+
codeStatus = "needs_approval";
|
|
1406
|
+
codeSummary = "Running scripts or code inherits the current exec approval requirement.";
|
|
1407
|
+
codeReasons.push("Interpreter and runtime commands inherit exec approval rules.");
|
|
1408
|
+
codeReasons.push("Approval-backed interpreter runs are conservative and may be denied when OpenClaw cannot bind one concrete file.");
|
|
1409
|
+
} else if (effectiveSecurity === "allowlist") {
|
|
1410
|
+
codeStatus = "restricted";
|
|
1411
|
+
codeSummary = "Running scripts is restricted by allowlist rules.";
|
|
1412
|
+
codeReasons.push("Interpreter and runtime commands usually need explicit allowlist entries.");
|
|
1413
|
+
} else {
|
|
1414
|
+
codeStatus = "available";
|
|
1415
|
+
codeSummary = "Code execution inherits the current command execution policy and looks available.";
|
|
1416
|
+
}
|
|
1417
|
+
if (safeBins.some(isInterpreterLikeSafeBin)) {
|
|
1418
|
+
codeStatus = codeStatus === "disabled" ? codeStatus : "restricted";
|
|
1419
|
+
codeReasons.push("Interpreter/runtime binaries should not rely on safeBins alone.");
|
|
1420
|
+
}
|
|
1421
|
+
return {
|
|
1422
|
+
configPath: openclaw.configPath,
|
|
1423
|
+
approvalsPath,
|
|
1424
|
+
web: {
|
|
1425
|
+
status: webStatus,
|
|
1426
|
+
summary: webSummary,
|
|
1427
|
+
reasons: uniqueSortedStrings(webReasons),
|
|
1428
|
+
searchEnabled,
|
|
1429
|
+
searchProvider,
|
|
1430
|
+
searchConfigured,
|
|
1431
|
+
fetchEnabled: readBoolean(webFetch?.enabled, true),
|
|
1432
|
+
firecrawlConfigured
|
|
1433
|
+
},
|
|
1434
|
+
exec: {
|
|
1435
|
+
currentAgentId: currentAgent.id,
|
|
1436
|
+
currentAgentName: currentAgent.name,
|
|
1437
|
+
toolProfile,
|
|
1438
|
+
execToolAvailable,
|
|
1439
|
+
hostApprovalsApply: usesHostApprovals,
|
|
1440
|
+
implicitSandboxFallback,
|
|
1441
|
+
status: execStatus,
|
|
1442
|
+
summary: execSummary,
|
|
1443
|
+
reasons: uniqueSortedStrings(execReasons),
|
|
1444
|
+
configuredHost,
|
|
1445
|
+
effectiveHost,
|
|
1446
|
+
sandboxMode,
|
|
1447
|
+
configSecurity,
|
|
1448
|
+
configAsk,
|
|
1449
|
+
approvalsExists: approvals != null,
|
|
1450
|
+
approvalsSecurity: resolvedApprovals.security,
|
|
1451
|
+
approvalsAsk: resolvedApprovals.ask,
|
|
1452
|
+
effectiveSecurity,
|
|
1453
|
+
effectiveAsk,
|
|
1454
|
+
allowlistCount: resolvedApprovals.allowlistCount,
|
|
1455
|
+
toolPolicyDenied,
|
|
1456
|
+
safeBins,
|
|
1457
|
+
safeBinTrustedDirs,
|
|
1458
|
+
trustedDirWarnings
|
|
1459
|
+
},
|
|
1460
|
+
codeExecution: {
|
|
1461
|
+
status: codeStatus,
|
|
1462
|
+
summary: codeSummary,
|
|
1463
|
+
reasons: uniqueSortedStrings(codeReasons),
|
|
1464
|
+
inheritsFromExec: true
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
async function runOpenClawDoctor() {
|
|
1469
|
+
const openclaw = resolveOpenClawPaths();
|
|
1470
|
+
try {
|
|
1471
|
+
const { stdout, stderr } = await runOpenClawCli(["doctor", "--json"], openclaw);
|
|
1472
|
+
try {
|
|
1473
|
+
const parsed = parseEmbeddedJsonValue(collectCliOutput([stdout, stderr]));
|
|
1474
|
+
const checks = Array.isArray(parsed.checks) ? parsed.checks.filter((c) => typeof c === "object" && c != null).map((c) => ({
|
|
1475
|
+
name: typeof c.name === "string" ? c.name : "unknown",
|
|
1476
|
+
status: typeof c.status === "string" ? c.status : "unknown",
|
|
1477
|
+
message: typeof c.message === "string" ? c.message : void 0
|
|
1478
|
+
})) : [];
|
|
1479
|
+
return {
|
|
1480
|
+
ok: parsed.ok === true,
|
|
1481
|
+
checks,
|
|
1482
|
+
summary: typeof parsed.summary === "string" ? parsed.summary : ""
|
|
1483
|
+
};
|
|
1484
|
+
} catch {
|
|
1485
|
+
return {
|
|
1486
|
+
ok: true,
|
|
1487
|
+
checks: [],
|
|
1488
|
+
summary: "",
|
|
1489
|
+
raw: collectCliOutput([stdout, stderr])
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
} catch (error) {
|
|
1493
|
+
if (isExecFileError(error)) {
|
|
1494
|
+
const output = collectCliOutput([error.stdout, error.stderr]);
|
|
1495
|
+
if (output) {
|
|
1496
|
+
if (isUnsupportedJsonOptionOutput(output)) {
|
|
1497
|
+
} else {
|
|
1498
|
+
try {
|
|
1499
|
+
const parsed = parseEmbeddedJsonValue(output);
|
|
1500
|
+
const checks = Array.isArray(parsed.checks) ? parsed.checks.filter((c) => typeof c === "object" && c != null).map((c) => ({
|
|
1501
|
+
name: typeof c.name === "string" ? c.name : "unknown",
|
|
1502
|
+
status: typeof c.status === "string" ? c.status : "unknown",
|
|
1503
|
+
message: typeof c.message === "string" ? c.message : void 0
|
|
1504
|
+
})) : [];
|
|
1505
|
+
return {
|
|
1506
|
+
ok: parsed.ok === true,
|
|
1507
|
+
checks,
|
|
1508
|
+
summary: typeof parsed.summary === "string" ? parsed.summary : ""
|
|
1509
|
+
};
|
|
1510
|
+
} catch {
|
|
1511
|
+
return {
|
|
1512
|
+
ok: false,
|
|
1513
|
+
checks: [],
|
|
1514
|
+
summary: "",
|
|
1515
|
+
raw: output
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
try {
|
|
1522
|
+
const { stdout, stderr } = await runOpenClawCli(["doctor"], openclaw);
|
|
1523
|
+
return {
|
|
1524
|
+
ok: true,
|
|
1525
|
+
checks: [],
|
|
1526
|
+
summary: "",
|
|
1527
|
+
raw: collectCliOutput([stdout, stderr])
|
|
1528
|
+
};
|
|
1529
|
+
} catch (fallbackError) {
|
|
1530
|
+
if (isExecFileError(fallbackError)) {
|
|
1531
|
+
const output = collectCliOutput([fallbackError.stdout, fallbackError.stderr]);
|
|
1532
|
+
if (output) {
|
|
1533
|
+
return {
|
|
1534
|
+
ok: false,
|
|
1535
|
+
checks: [],
|
|
1536
|
+
summary: "",
|
|
1537
|
+
raw: output
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
throw fallbackError instanceof Error ? fallbackError : formatOpenClawCliError(["doctor"], fallbackError);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
async function runOpenClawDoctorFix() {
|
|
1546
|
+
const openclaw = resolveOpenClawPaths();
|
|
1547
|
+
try {
|
|
1548
|
+
const { stdout, stderr } = await runOpenClawCli(["doctor", "--fix"], openclaw);
|
|
1549
|
+
return {
|
|
1550
|
+
ok: true,
|
|
1551
|
+
summary: "",
|
|
1552
|
+
raw: collectCliOutput([stdout, stderr])
|
|
1553
|
+
};
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
if (isExecFileError(error)) {
|
|
1556
|
+
const output = collectCliOutput([error.stdout, error.stderr]);
|
|
1557
|
+
if (output) {
|
|
1558
|
+
return {
|
|
1559
|
+
ok: false,
|
|
1560
|
+
summary: "",
|
|
1561
|
+
raw: output
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
throw formatOpenClawCliError(["doctor", "--fix"], error);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1236
1568
|
async function restartOpenClawGateway() {
|
|
1237
1569
|
const openclaw = resolveOpenClawPaths();
|
|
1238
1570
|
const restart = await runOpenClawDaemonCommand(["gateway", "restart", "--json"], openclaw);
|
|
@@ -1292,6 +1624,226 @@ async function issueOpenClawBootstrapToken(params) {
|
|
|
1292
1624
|
function readConfiguredSecret(value) {
|
|
1293
1625
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1294
1626
|
}
|
|
1627
|
+
function readRecord(value) {
|
|
1628
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
return value;
|
|
1632
|
+
}
|
|
1633
|
+
function readString(value) {
|
|
1634
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1635
|
+
}
|
|
1636
|
+
function readBoolean(value, fallback) {
|
|
1637
|
+
return typeof value === "boolean" ? value : fallback;
|
|
1638
|
+
}
|
|
1639
|
+
function readStringArray(value) {
|
|
1640
|
+
if (!Array.isArray(value)) {
|
|
1641
|
+
return [];
|
|
1642
|
+
}
|
|
1643
|
+
return uniqueSortedStrings(value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()));
|
|
1644
|
+
}
|
|
1645
|
+
function uniqueSortedStrings(values) {
|
|
1646
|
+
return [...new Set(values.filter((value) => value.trim().length > 0))].sort();
|
|
1647
|
+
}
|
|
1648
|
+
function resolveCurrentAgent(config) {
|
|
1649
|
+
const agents = readRecord(config?.agents);
|
|
1650
|
+
const list = Array.isArray(agents?.list) ? agents.list : [];
|
|
1651
|
+
const entries = list.map((entry) => readRecord(entry)).filter((entry) => entry != null);
|
|
1652
|
+
const target = entries.find((entry) => entry.default === true) ?? entries.find((entry) => readString(entry.id) === "main") ?? entries[0] ?? null;
|
|
1653
|
+
const id = readString(target?.id) ?? "main";
|
|
1654
|
+
const name = readString(target?.name) ?? id;
|
|
1655
|
+
return { id, name, record: target };
|
|
1656
|
+
}
|
|
1657
|
+
function resolveToolProfile(globalTools, agentTools) {
|
|
1658
|
+
const raw = readString(agentTools?.profile) ?? readString(globalTools?.profile);
|
|
1659
|
+
return raw === "minimal" || raw === "coding" || raw === "messaging" || raw === "full" ? raw : "unset";
|
|
1660
|
+
}
|
|
1661
|
+
function toolProfileAllowsExec(profile) {
|
|
1662
|
+
return profile === "coding" || profile === "full" || profile === "unset";
|
|
1663
|
+
}
|
|
1664
|
+
function deniesExecTool(denyList) {
|
|
1665
|
+
const normalized = new Set(denyList.map((entry) => entry.trim().toLowerCase()).filter(Boolean));
|
|
1666
|
+
return normalized.has("*") || normalized.has("exec") || normalized.has("bash") || normalized.has("group:runtime");
|
|
1667
|
+
}
|
|
1668
|
+
function resolveMergedExecConfig(globalTools, agentTools) {
|
|
1669
|
+
const globalExec = readRecord(globalTools?.exec);
|
|
1670
|
+
const agentExec = readRecord(agentTools?.exec);
|
|
1671
|
+
if (!globalExec && !agentExec) {
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1674
|
+
return {
|
|
1675
|
+
...globalExec ?? {},
|
|
1676
|
+
...agentExec ?? {}
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
function readExecHost(value) {
|
|
1680
|
+
return value === "gateway" || value === "node" || value === "sandbox" ? value : "sandbox";
|
|
1681
|
+
}
|
|
1682
|
+
function readSandboxMode(config, agentId = "main") {
|
|
1683
|
+
const agents = readRecord(config?.agents);
|
|
1684
|
+
const defaults = readRecord(agents?.defaults);
|
|
1685
|
+
const defaultSandbox = readRecord(defaults?.sandbox);
|
|
1686
|
+
const list = Array.isArray(agents?.list) ? agents.list : [];
|
|
1687
|
+
const targetAgent = list.find((entry) => {
|
|
1688
|
+
const record = readRecord(entry);
|
|
1689
|
+
return readString(record?.id) === agentId;
|
|
1690
|
+
});
|
|
1691
|
+
const agentSandbox = readRecord(readRecord(targetAgent)?.sandbox);
|
|
1692
|
+
const value = readString(agentSandbox?.mode) ?? readString(defaultSandbox?.mode);
|
|
1693
|
+
return value === "all" || value === "non-main" || value === "off" ? value : "off";
|
|
1694
|
+
}
|
|
1695
|
+
function readExecSecurity(value, fallback) {
|
|
1696
|
+
return value === "deny" || value === "allowlist" || value === "full" ? value : fallback;
|
|
1697
|
+
}
|
|
1698
|
+
function readExecAsk(value, fallback) {
|
|
1699
|
+
return value === "off" || value === "on-miss" || value === "always" ? value : fallback;
|
|
1700
|
+
}
|
|
1701
|
+
function minExecSecurity(a, b) {
|
|
1702
|
+
const order = { deny: 0, allowlist: 1, full: 2 };
|
|
1703
|
+
return order[a] <= order[b] ? a : b;
|
|
1704
|
+
}
|
|
1705
|
+
function maxExecAsk(a, b) {
|
|
1706
|
+
const order = { off: 0, "on-miss": 1, always: 2 };
|
|
1707
|
+
return order[a] >= order[b] ? a : b;
|
|
1708
|
+
}
|
|
1709
|
+
function resolveWebSearchConfigured(webSearch, provider, configEnv) {
|
|
1710
|
+
const braveKey = readConfiguredSecret(webSearch?.apiKey) ?? readConfigEnvValue(configEnv, "BRAVE_API_KEY") ?? readEnvValue("BRAVE_API_KEY", "BRAVE_API_KEY");
|
|
1711
|
+
const gemini = readRecord(webSearch?.gemini);
|
|
1712
|
+
const geminiKey = readConfiguredSecret(gemini?.apiKey) ?? readConfigEnvValue(configEnv, "GEMINI_API_KEY") ?? readEnvValue("GEMINI_API_KEY", "GEMINI_API_KEY");
|
|
1713
|
+
const grok = readRecord(webSearch?.grok);
|
|
1714
|
+
const grokKey = readConfiguredSecret(grok?.apiKey) ?? readConfigEnvValue(configEnv, "XAI_API_KEY") ?? readEnvValue("XAI_API_KEY", "XAI_API_KEY");
|
|
1715
|
+
const kimi = readRecord(webSearch?.kimi);
|
|
1716
|
+
const kimiKey = readConfiguredSecret(kimi?.apiKey) ?? readConfigEnvValue(configEnv, "KIMI_API_KEY", "MOONSHOT_API_KEY") ?? readConfigEnvValue(configEnv, "MOONSHOT_API_KEY", "KIMI_API_KEY") ?? readEnvValue("KIMI_API_KEY", "MOONSHOT_API_KEY") ?? readEnvValue("MOONSHOT_API_KEY", "KIMI_API_KEY");
|
|
1717
|
+
const perplexity = readRecord(webSearch?.perplexity);
|
|
1718
|
+
const perplexityKey = readConfiguredSecret(perplexity?.apiKey) ?? readConfigEnvValue(configEnv, "PERPLEXITY_API_KEY", "OPENROUTER_API_KEY") ?? readConfigEnvValue(configEnv, "OPENROUTER_API_KEY", "PERPLEXITY_API_KEY") ?? readEnvValue("PERPLEXITY_API_KEY", "OPENROUTER_API_KEY") ?? readEnvValue("OPENROUTER_API_KEY", "PERPLEXITY_API_KEY");
|
|
1719
|
+
const configuredByProvider = {
|
|
1720
|
+
brave: Boolean(braveKey),
|
|
1721
|
+
gemini: Boolean(geminiKey),
|
|
1722
|
+
grok: Boolean(grokKey),
|
|
1723
|
+
kimi: Boolean(kimiKey),
|
|
1724
|
+
perplexity: Boolean(perplexityKey)
|
|
1725
|
+
};
|
|
1726
|
+
if (provider !== "auto") {
|
|
1727
|
+
return configuredByProvider[provider] === true;
|
|
1728
|
+
}
|
|
1729
|
+
return Object.values(configuredByProvider).some(Boolean);
|
|
1730
|
+
}
|
|
1731
|
+
function resolveFirecrawlConfigured(webFetch, configEnv) {
|
|
1732
|
+
const firecrawl = readRecord(webFetch?.firecrawl);
|
|
1733
|
+
return Boolean(readConfiguredSecret(firecrawl?.apiKey) ?? readConfigEnvValue(configEnv, "FIRECRAWL_API_KEY") ?? readEnvValue("FIRECRAWL_API_KEY", "FIRECRAWL_API_KEY"));
|
|
1734
|
+
}
|
|
1735
|
+
function readConfigEnvVars(config) {
|
|
1736
|
+
const env = readRecord(config?.env);
|
|
1737
|
+
if (!env) {
|
|
1738
|
+
return {};
|
|
1739
|
+
}
|
|
1740
|
+
const vars = readRecord(env.vars);
|
|
1741
|
+
const result = {};
|
|
1742
|
+
if (vars) {
|
|
1743
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
1744
|
+
const normalized = key.trim();
|
|
1745
|
+
if (!normalized) {
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
const secret = readConfiguredSecret(value);
|
|
1749
|
+
if (secret) {
|
|
1750
|
+
result[normalized] = secret;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
for (const [key, value] of Object.entries(env)) {
|
|
1755
|
+
if (key === "vars" || key === "shellEnv") {
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
const normalized = key.trim();
|
|
1759
|
+
if (!normalized) {
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1762
|
+
const secret = readConfiguredSecret(value);
|
|
1763
|
+
if (secret) {
|
|
1764
|
+
result[normalized] = secret;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
return result;
|
|
1768
|
+
}
|
|
1769
|
+
function readConfigEnvValue(configEnv, primary, legacy) {
|
|
1770
|
+
const current = configEnv[primary]?.trim();
|
|
1771
|
+
if (current) {
|
|
1772
|
+
return current;
|
|
1773
|
+
}
|
|
1774
|
+
if (!legacy) {
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
const fallback = configEnv[legacy]?.trim();
|
|
1778
|
+
return fallback || null;
|
|
1779
|
+
}
|
|
1780
|
+
function resolveExecApprovalsSummary(approvals, overrides) {
|
|
1781
|
+
const defaults = readRecord(approvals?.defaults);
|
|
1782
|
+
const agents = readRecord(approvals?.agents);
|
|
1783
|
+
const wildcard = readRecord(agents?.["*"]);
|
|
1784
|
+
const main2 = readRecord(agents?.main) ?? readRecord(agents?.default);
|
|
1785
|
+
const security = readExecSecurity(main2?.security ?? wildcard?.security ?? defaults?.security, readExecSecurity(defaults?.security, overrides.security));
|
|
1786
|
+
const ask = readExecAsk(main2?.ask ?? wildcard?.ask ?? defaults?.ask, readExecAsk(defaults?.ask, overrides.ask));
|
|
1787
|
+
const allowlistEntries = [
|
|
1788
|
+
...readAllowlistEntries(wildcard?.allowlist),
|
|
1789
|
+
...readAllowlistEntries(main2?.allowlist)
|
|
1790
|
+
];
|
|
1791
|
+
return {
|
|
1792
|
+
security,
|
|
1793
|
+
ask,
|
|
1794
|
+
allowlistCount: uniqueSortedStrings(allowlistEntries.map((entry) => entry.pattern)).length
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
function readAllowlistEntries(value) {
|
|
1798
|
+
if (!Array.isArray(value)) {
|
|
1799
|
+
return [];
|
|
1800
|
+
}
|
|
1801
|
+
const entries = [];
|
|
1802
|
+
for (const entry of value) {
|
|
1803
|
+
const record = readRecord(entry);
|
|
1804
|
+
const pattern = readString(record?.pattern);
|
|
1805
|
+
if (pattern) {
|
|
1806
|
+
entries.push({ pattern });
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
return entries;
|
|
1810
|
+
}
|
|
1811
|
+
function isInterpreterLikeSafeBin(value) {
|
|
1812
|
+
const normalized = value.trim().toLowerCase().split(/[\\/]/).at(-1) ?? "";
|
|
1813
|
+
if (!normalized) {
|
|
1814
|
+
return false;
|
|
1815
|
+
}
|
|
1816
|
+
if ([
|
|
1817
|
+
"ash",
|
|
1818
|
+
"bash",
|
|
1819
|
+
"bun",
|
|
1820
|
+
"cmd",
|
|
1821
|
+
"cmd.exe",
|
|
1822
|
+
"dash",
|
|
1823
|
+
"deno",
|
|
1824
|
+
"fish",
|
|
1825
|
+
"ksh",
|
|
1826
|
+
"lua",
|
|
1827
|
+
"node",
|
|
1828
|
+
"nodejs",
|
|
1829
|
+
"perl",
|
|
1830
|
+
"php",
|
|
1831
|
+
"powershell",
|
|
1832
|
+
"powershell.exe",
|
|
1833
|
+
"pypy",
|
|
1834
|
+
"pwsh",
|
|
1835
|
+
"pwsh.exe",
|
|
1836
|
+
"python",
|
|
1837
|
+
"python2",
|
|
1838
|
+
"python3",
|
|
1839
|
+
"ruby",
|
|
1840
|
+
"sh",
|
|
1841
|
+
"zsh"
|
|
1842
|
+
].includes(normalized)) {
|
|
1843
|
+
return true;
|
|
1844
|
+
}
|
|
1845
|
+
return /^(python|ruby|perl|php|node)\d+(?:\.\d+)?$/.test(normalized);
|
|
1846
|
+
}
|
|
1295
1847
|
async function readOpenClawConfigString(configPath, openclaw = resolveOpenClawPaths()) {
|
|
1296
1848
|
try {
|
|
1297
1849
|
const { stdout } = await runOpenClawCli(["config", "get", configPath], openclaw);
|
|
@@ -1358,7 +1910,7 @@ async function runOpenClawCli(args, openclaw = resolveOpenClawPaths()) {
|
|
|
1358
1910
|
});
|
|
1359
1911
|
});
|
|
1360
1912
|
} catch (error) {
|
|
1361
|
-
throw formatOpenClawCliError(args, error);
|
|
1913
|
+
throw preserveExecFileErrorDetails(formatOpenClawCliError(args, error), error);
|
|
1362
1914
|
}
|
|
1363
1915
|
}
|
|
1364
1916
|
function formatOpenClawCliError(args, error) {
|
|
@@ -1372,6 +1924,16 @@ function formatOpenClawCliError(args, error) {
|
|
|
1372
1924
|
}
|
|
1373
1925
|
return error instanceof Error ? error : new Error(`${messagePrefix} failed: ${String(error)}`);
|
|
1374
1926
|
}
|
|
1927
|
+
function preserveExecFileErrorDetails(formatted, original) {
|
|
1928
|
+
if (!isExecFileError(original)) {
|
|
1929
|
+
return formatted;
|
|
1930
|
+
}
|
|
1931
|
+
return Object.assign(formatted, {
|
|
1932
|
+
code: original.code,
|
|
1933
|
+
stdout: original.stdout,
|
|
1934
|
+
stderr: original.stderr
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1375
1937
|
function isMissingConfigPathError(error) {
|
|
1376
1938
|
return error instanceof Error && /Config path not found:/i.test(error.message);
|
|
1377
1939
|
}
|
|
@@ -1499,6 +2061,13 @@ function readGatewayTokenEnv() {
|
|
|
1499
2061
|
function readGatewayPasswordEnv() {
|
|
1500
2062
|
return readEnvValue("OPENCLAW_GATEWAY_PASSWORD", "CLAWDBOT_GATEWAY_PASSWORD");
|
|
1501
2063
|
}
|
|
2064
|
+
function readGatewayPortEnv() {
|
|
2065
|
+
const raw = readEnvValue("OPENCLAW_GATEWAY_PORT", "CLAWDBOT_GATEWAY_PORT");
|
|
2066
|
+
if (!raw)
|
|
2067
|
+
return null;
|
|
2068
|
+
const parsed = Number.parseInt(raw, 10);
|
|
2069
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
2070
|
+
}
|
|
1502
2071
|
function readEnvValue(primary, legacy) {
|
|
1503
2072
|
const current = process.env[primary]?.trim();
|
|
1504
2073
|
if (current)
|
|
@@ -1516,6 +2085,9 @@ function resolveOpenClawPaths() {
|
|
|
1516
2085
|
mediaDir: join3(stateDir, "media")
|
|
1517
2086
|
};
|
|
1518
2087
|
}
|
|
2088
|
+
function resolveOpenClawExecApprovalsPath() {
|
|
2089
|
+
return join3(resolveOpenClawHomeDir(), ".openclaw", "exec-approvals.json");
|
|
2090
|
+
}
|
|
1519
2091
|
function resolveActiveOpenClawStateDir() {
|
|
1520
2092
|
const explicitStateDir = readEnvValue("OPENCLAW_STATE_DIR", "CLAWDBOT_STATE_DIR");
|
|
1521
2093
|
if (explicitStateDir) {
|
|
@@ -1541,6 +2113,21 @@ function buildOpenClawStateDirCandidates() {
|
|
|
1541
2113
|
return true;
|
|
1542
2114
|
});
|
|
1543
2115
|
}
|
|
2116
|
+
function resolveOpenClawHomeDir() {
|
|
2117
|
+
const explicitHome = process.env.OPENCLAW_HOME?.trim();
|
|
2118
|
+
if (explicitHome) {
|
|
2119
|
+
return resolveUserPath(explicitHome);
|
|
2120
|
+
}
|
|
2121
|
+
const envHome = process.env.HOME?.trim();
|
|
2122
|
+
if (envHome) {
|
|
2123
|
+
return resolveUserPath(envHome);
|
|
2124
|
+
}
|
|
2125
|
+
const userProfile = process.env.USERPROFILE?.trim();
|
|
2126
|
+
if (userProfile) {
|
|
2127
|
+
return resolveUserPath(userProfile);
|
|
2128
|
+
}
|
|
2129
|
+
return resolve(homedir3());
|
|
2130
|
+
}
|
|
1544
2131
|
function resolveUserPath(input) {
|
|
1545
2132
|
const trimmed = input.trim();
|
|
1546
2133
|
if (!trimmed)
|
|
@@ -1955,6 +2542,18 @@ var BridgeRuntime = class {
|
|
|
1955
2542
|
await this.handleBootstrapRequest(control);
|
|
1956
2543
|
return;
|
|
1957
2544
|
}
|
|
2545
|
+
if (control.event === "doctor.request") {
|
|
2546
|
+
await this.handleDoctorRequest(control);
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
if (control.event === "doctor-fix.request") {
|
|
2550
|
+
await this.handleDoctorFixRequest(control);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (control.event === "permissions.request") {
|
|
2554
|
+
await this.handlePermissionsRequest(control);
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
1958
2557
|
const { event, count } = control;
|
|
1959
2558
|
if (event === "client_connected" || event === "client_count") {
|
|
1960
2559
|
const previousClientCount = this.snapshot.clientCount;
|
|
@@ -2056,6 +2655,108 @@ var BridgeRuntime = class {
|
|
|
2056
2655
|
});
|
|
2057
2656
|
}
|
|
2058
2657
|
}
|
|
2658
|
+
async handleDoctorRequest(control) {
|
|
2659
|
+
const requestId = control.requestId?.trim() ?? "";
|
|
2660
|
+
const replyTargetClientId = control.sourceClientId?.trim() || control.targetClientId?.trim() || "";
|
|
2661
|
+
if (!requestId) {
|
|
2662
|
+
this.log("relay doctor request dropped reason=missing_request_id");
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
this.log(`relay doctor request received requestId=${requestId}`);
|
|
2666
|
+
try {
|
|
2667
|
+
const result = await runOpenClawDoctor();
|
|
2668
|
+
this.log(`relay doctor completed requestId=${requestId} ok=${result.ok} checks=${result.checks.length}`);
|
|
2669
|
+
this.sendRelayControl({
|
|
2670
|
+
event: "doctor.result",
|
|
2671
|
+
requestId,
|
|
2672
|
+
targetClientId: replyTargetClientId || void 0,
|
|
2673
|
+
payload: {
|
|
2674
|
+
ok: result.ok,
|
|
2675
|
+
checks: result.checks,
|
|
2676
|
+
summary: result.summary,
|
|
2677
|
+
raw: result.raw
|
|
2678
|
+
}
|
|
2679
|
+
});
|
|
2680
|
+
} catch (error) {
|
|
2681
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2682
|
+
this.log(`relay doctor failed requestId=${requestId} error=${message}`);
|
|
2683
|
+
this.sendRelayControl({
|
|
2684
|
+
event: "doctor.error",
|
|
2685
|
+
requestId,
|
|
2686
|
+
targetClientId: replyTargetClientId || void 0,
|
|
2687
|
+
payload: {
|
|
2688
|
+
code: "doctor_failed",
|
|
2689
|
+
message
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
async handleDoctorFixRequest(control) {
|
|
2695
|
+
const requestId = control.requestId?.trim() ?? "";
|
|
2696
|
+
const replyTargetClientId = control.sourceClientId?.trim() || control.targetClientId?.trim() || "";
|
|
2697
|
+
if (!requestId) {
|
|
2698
|
+
this.log("relay doctor-fix request dropped reason=missing_request_id");
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
this.log(`relay doctor-fix request received requestId=${requestId}`);
|
|
2702
|
+
try {
|
|
2703
|
+
const result = await runOpenClawDoctorFix();
|
|
2704
|
+
this.log(`relay doctor-fix completed requestId=${requestId} ok=${result.ok}`);
|
|
2705
|
+
this.sendRelayControl({
|
|
2706
|
+
event: "doctor-fix.result",
|
|
2707
|
+
requestId,
|
|
2708
|
+
targetClientId: replyTargetClientId || void 0,
|
|
2709
|
+
payload: {
|
|
2710
|
+
ok: result.ok,
|
|
2711
|
+
summary: result.summary,
|
|
2712
|
+
raw: result.raw
|
|
2713
|
+
}
|
|
2714
|
+
});
|
|
2715
|
+
} catch (error) {
|
|
2716
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2717
|
+
this.log(`relay doctor-fix failed requestId=${requestId} error=${message}`);
|
|
2718
|
+
this.sendRelayControl({
|
|
2719
|
+
event: "doctor-fix.error",
|
|
2720
|
+
requestId,
|
|
2721
|
+
targetClientId: replyTargetClientId || void 0,
|
|
2722
|
+
payload: {
|
|
2723
|
+
code: "doctor_fix_failed",
|
|
2724
|
+
message
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
async handlePermissionsRequest(control) {
|
|
2730
|
+
const requestId = control.requestId?.trim() ?? "";
|
|
2731
|
+
const replyTargetClientId = control.sourceClientId?.trim() || control.targetClientId?.trim() || "";
|
|
2732
|
+
if (!requestId) {
|
|
2733
|
+
this.log("relay permissions request dropped reason=missing_request_id");
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
this.log(`relay permissions request received requestId=${requestId}`);
|
|
2737
|
+
try {
|
|
2738
|
+
const result = await readOpenClawPermissions();
|
|
2739
|
+
this.log(`relay permissions completed requestId=${requestId} execStatus=${result.exec.status} webStatus=${result.web.status}`);
|
|
2740
|
+
this.sendRelayControl({
|
|
2741
|
+
event: "permissions.result",
|
|
2742
|
+
requestId,
|
|
2743
|
+
targetClientId: replyTargetClientId || void 0,
|
|
2744
|
+
payload: result
|
|
2745
|
+
});
|
|
2746
|
+
} catch (error) {
|
|
2747
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2748
|
+
this.log(`relay permissions failed requestId=${requestId} error=${message}`);
|
|
2749
|
+
this.sendRelayControl({
|
|
2750
|
+
event: "permissions.error",
|
|
2751
|
+
requestId,
|
|
2752
|
+
targetClientId: replyTargetClientId || void 0,
|
|
2753
|
+
payload: {
|
|
2754
|
+
code: "permissions_failed",
|
|
2755
|
+
message
|
|
2756
|
+
}
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2059
2760
|
forwardOrQueueGatewayMessage(message) {
|
|
2060
2761
|
const isConnectHandshake = message.kind === "text" && isConnectHandshakeRequest(message.text);
|
|
2061
2762
|
if (this.gatewayCloseBoundaryPending) {
|
|
@@ -2612,7 +3313,7 @@ function redactAuthorizationHeader(value) {
|
|
|
2612
3313
|
return /^Bearer\s+/i.test(trimmed) ? "Bearer <redacted>" : "<redacted>";
|
|
2613
3314
|
}
|
|
2614
3315
|
function sanitizeRuntimeLogLine(line) {
|
|
2615
|
-
return line;
|
|
3316
|
+
return line.replace(/\b(instanceId|clientId|sourceClientId|targetClientId|deviceId|requestId|reqId|traceId)=([^\s]+)/g, "$1=<redacted>").replace(/\b(relay|client)=([^\s]+)/g, "$1=<redacted>").replace(/\b(grs_[A-Za-z0-9_-]+|gct_[A-Za-z0-9_-]+)\b/g, "<redacted>");
|
|
2616
3317
|
}
|
|
2617
3318
|
function redactGatewayWsUrl(rawUrl) {
|
|
2618
3319
|
const parsed = new URL(rawUrl);
|