@clawos-dev/clawd 0.2.158-beta.332.393cc6b → 0.2.159-beta.333.770f863
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/dist/cli.cjs +136 -41
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -40408,6 +40408,28 @@ async function handleRpcRequest(input) {
|
|
|
40408
40408
|
// src/transport/http-router.ts
|
|
40409
40409
|
var RPC_ROUTE_PREFIX = "/api/rpc/";
|
|
40410
40410
|
var RPC_BODY_MAX_BYTES = 1 * 1024 * 1024;
|
|
40411
|
+
var AUTHED_RPC_ROUTE_PREFIX = "/rpc/";
|
|
40412
|
+
var AUTHED_RPC_CORS_HEADERS = {
|
|
40413
|
+
"Access-Control-Allow-Origin": "*",
|
|
40414
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
40415
|
+
"Access-Control-Allow-Headers": "Authorization, Content-Type",
|
|
40416
|
+
"Access-Control-Max-Age": "86400"
|
|
40417
|
+
};
|
|
40418
|
+
function authedRpcErrorStatus(code) {
|
|
40419
|
+
switch (code) {
|
|
40420
|
+
case "UNAUTHORIZED":
|
|
40421
|
+
case "FORBIDDEN":
|
|
40422
|
+
case "FEISHU_LOGIN_REQUIRED":
|
|
40423
|
+
return 403;
|
|
40424
|
+
case "METHOD_NOT_ALLOWED":
|
|
40425
|
+
case "METHOD_NOT_IMPLEMENTED":
|
|
40426
|
+
return 404;
|
|
40427
|
+
case "INTERNAL":
|
|
40428
|
+
return 500;
|
|
40429
|
+
default:
|
|
40430
|
+
return 400;
|
|
40431
|
+
}
|
|
40432
|
+
}
|
|
40411
40433
|
function isLoopbackAddress(addr) {
|
|
40412
40434
|
if (!addr) return false;
|
|
40413
40435
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1" || addr.startsWith("127.");
|
|
@@ -40502,6 +40524,52 @@ function createHttpRouter(deps) {
|
|
|
40502
40524
|
res.end(html);
|
|
40503
40525
|
return true;
|
|
40504
40526
|
}
|
|
40527
|
+
if (url.pathname.startsWith(AUTHED_RPC_ROUTE_PREFIX)) {
|
|
40528
|
+
if (req.method === "OPTIONS") {
|
|
40529
|
+
res.writeHead(204, AUTHED_RPC_CORS_HEADERS);
|
|
40530
|
+
res.end();
|
|
40531
|
+
return true;
|
|
40532
|
+
}
|
|
40533
|
+
if (!deps.authedRpc) {
|
|
40534
|
+
sendJson(res, 501, { ok: false, error: "NOT_IMPLEMENTED", message: "HTTP RPC not wired" }, AUTHED_RPC_CORS_HEADERS);
|
|
40535
|
+
return true;
|
|
40536
|
+
}
|
|
40537
|
+
if (req.method !== "POST") {
|
|
40538
|
+
sendJson(res, 405, { ok: false, error: "METHOD_NOT_ALLOWED", message: "POST only" }, AUTHED_RPC_CORS_HEADERS);
|
|
40539
|
+
return true;
|
|
40540
|
+
}
|
|
40541
|
+
const token = parseBearer(req.headers.authorization);
|
|
40542
|
+
if (!token) {
|
|
40543
|
+
sendJson(res, 401, { ok: false, error: "NO_TOKEN", message: "missing bearer token" }, AUTHED_RPC_CORS_HEADERS);
|
|
40544
|
+
return true;
|
|
40545
|
+
}
|
|
40546
|
+
const auth = await deps.authedRpc.authenticate(token);
|
|
40547
|
+
if (!auth.ok) {
|
|
40548
|
+
sendJson(res, 401, { ok: false, error: auth.code, message: "invalid token" }, AUTHED_RPC_CORS_HEADERS);
|
|
40549
|
+
return true;
|
|
40550
|
+
}
|
|
40551
|
+
const method = url.pathname.slice(AUTHED_RPC_ROUTE_PREFIX.length).split("?")[0] ?? "";
|
|
40552
|
+
let body;
|
|
40553
|
+
try {
|
|
40554
|
+
body = await readJsonBody(req, RPC_BODY_MAX_BYTES);
|
|
40555
|
+
} catch (err) {
|
|
40556
|
+
sendJson(res, 400, { ok: false, error: "INVALID_BODY", message: err instanceof Error ? err.message : String(err) }, AUTHED_RPC_CORS_HEADERS);
|
|
40557
|
+
return true;
|
|
40558
|
+
}
|
|
40559
|
+
try {
|
|
40560
|
+
const result = await deps.authedRpc.dispatch(
|
|
40561
|
+
method,
|
|
40562
|
+
typeof body === "object" && body != null ? body : {},
|
|
40563
|
+
auth.context
|
|
40564
|
+
);
|
|
40565
|
+
sendJson(res, 200, { ok: true, result: result.response }, AUTHED_RPC_CORS_HEADERS);
|
|
40566
|
+
} catch (err) {
|
|
40567
|
+
const e = err;
|
|
40568
|
+
const code = e?.code ?? "INTERNAL";
|
|
40569
|
+
sendJson(res, authedRpcErrorStatus(code), { ok: false, error: code, message: e?.message ?? String(err) }, AUTHED_RPC_CORS_HEADERS);
|
|
40570
|
+
}
|
|
40571
|
+
return true;
|
|
40572
|
+
}
|
|
40505
40573
|
if (url.pathname.startsWith(RPC_ROUTE_PREFIX)) {
|
|
40506
40574
|
if (!deps.rpcDispatcher) {
|
|
40507
40575
|
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "HTTP RPC adapter not wired" });
|
|
@@ -45838,6 +45906,9 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
|
45838
45906
|
}
|
|
45839
45907
|
};
|
|
45840
45908
|
|
|
45909
|
+
// src/handlers/dispatch-rpc.ts
|
|
45910
|
+
init_protocol();
|
|
45911
|
+
|
|
45841
45912
|
// src/handlers/method-grants.ts
|
|
45842
45913
|
var ADMIN_ANY = {
|
|
45843
45914
|
kind: "fixed",
|
|
@@ -46013,6 +46084,32 @@ function computeGrantForFrame(method, frame) {
|
|
|
46013
46084
|
return { kind: "check", resource: picked.resource, action: picked.action };
|
|
46014
46085
|
}
|
|
46015
46086
|
|
|
46087
|
+
// src/handlers/dispatch-rpc.ts
|
|
46088
|
+
async function dispatchRpc(method, frame, client, ctx, deps) {
|
|
46089
|
+
if (!METHOD_NAMES.includes(method)) {
|
|
46090
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_ALLOWED, `unknown method: ${method}`);
|
|
46091
|
+
}
|
|
46092
|
+
const handler = deps.handlers[method];
|
|
46093
|
+
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${method}`);
|
|
46094
|
+
if (!deps.feishuActive() && FEISHU_GATED_METHODS.includes(method)) {
|
|
46095
|
+
throw new ClawdError(
|
|
46096
|
+
ERROR_CODES.FEISHU_LOGIN_REQUIRED,
|
|
46097
|
+
`${method} requires feishu login (People/remote identity)`
|
|
46098
|
+
);
|
|
46099
|
+
}
|
|
46100
|
+
const verdict = computeGrantForFrame(method, frame);
|
|
46101
|
+
if (verdict.kind === "check") {
|
|
46102
|
+
const ok = assertGrant(ctx.grants, verdict.resource, verdict.action);
|
|
46103
|
+
if (!ok) {
|
|
46104
|
+
throw new ClawdError(
|
|
46105
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
46106
|
+
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${verdict.action} on ${verdict.resource.type}${"id" in verdict.resource ? ":" + verdict.resource.id : ""}`
|
|
46107
|
+
);
|
|
46108
|
+
}
|
|
46109
|
+
}
|
|
46110
|
+
return handler(frame, client, ctx);
|
|
46111
|
+
}
|
|
46112
|
+
|
|
46016
46113
|
// src/extension/runtime.ts
|
|
46017
46114
|
var import_node_child_process15 = require("child_process");
|
|
46018
46115
|
var import_node_path45 = __toESM(require("path"), 1);
|
|
@@ -46419,6 +46516,22 @@ async function startDaemon(config) {
|
|
|
46419
46516
|
const contactStore = new ContactStore(config.dataDir);
|
|
46420
46517
|
contactStore.load();
|
|
46421
46518
|
const serverKeyStore = new ServerKeyStore(config.dataDir);
|
|
46519
|
+
const buildConnectAuthDeps = () => ({
|
|
46520
|
+
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
46521
|
+
ownerPrincipalId,
|
|
46522
|
+
ownerDisplayName,
|
|
46523
|
+
...feishuActive ? {
|
|
46524
|
+
verifyConnectToken: (token) => {
|
|
46525
|
+
const publicKeyPem = serverKeyStore.read();
|
|
46526
|
+
if (!publicKeyPem) return { ok: false, reason: "BAD_SIGNATURE" };
|
|
46527
|
+
return verifyConnectToken({
|
|
46528
|
+
token,
|
|
46529
|
+
publicKeyPem,
|
|
46530
|
+
expectedDeviceId: authFile.deviceId
|
|
46531
|
+
});
|
|
46532
|
+
}
|
|
46533
|
+
} : {}
|
|
46534
|
+
});
|
|
46422
46535
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
46423
46536
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
46424
46537
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
@@ -46447,25 +46560,7 @@ async function startDaemon(config) {
|
|
|
46447
46560
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
46448
46561
|
expectedToken: resolvedAuthToken,
|
|
46449
46562
|
authenticate: async (t, selfUrl) => {
|
|
46450
|
-
const result = await authenticate(t,
|
|
46451
|
-
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
46452
|
-
ownerPrincipalId,
|
|
46453
|
-
ownerDisplayName,
|
|
46454
|
-
// 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest 入站(不注入验签函数
|
|
46455
|
-
// → 一律 BAD_TOKEN)。已激活时用缓存的 server 公钥本地验签;
|
|
46456
|
-
// 公钥缓存缺失(登录后拉取失败)→ fail-closed 拒绝 guest。
|
|
46457
|
-
...feishuActive ? {
|
|
46458
|
-
verifyConnectToken: (token) => {
|
|
46459
|
-
const publicKeyPem = serverKeyStore.read();
|
|
46460
|
-
if (!publicKeyPem) return { ok: false, reason: "BAD_SIGNATURE" };
|
|
46461
|
-
return verifyConnectToken({
|
|
46462
|
-
token,
|
|
46463
|
-
publicKeyPem,
|
|
46464
|
-
expectedDeviceId: authFile.deviceId
|
|
46465
|
-
});
|
|
46466
|
-
}
|
|
46467
|
-
} : {}
|
|
46468
|
-
});
|
|
46563
|
+
const result = await authenticate(t, buildConnectAuthDeps());
|
|
46469
46564
|
if (result.ok && result.context.principal.kind === "guest") {
|
|
46470
46565
|
void autoReverseContact({
|
|
46471
46566
|
store: contactStore,
|
|
@@ -47019,6 +47114,23 @@ async function startDaemon(config) {
|
|
|
47019
47114
|
return result.response;
|
|
47020
47115
|
}
|
|
47021
47116
|
};
|
|
47117
|
+
const authedRpc = {
|
|
47118
|
+
authenticate: (token) => authenticate(token, buildConnectAuthDeps()),
|
|
47119
|
+
dispatch: (method, body, ctx) => {
|
|
47120
|
+
const client = {
|
|
47121
|
+
id: "http-rpc",
|
|
47122
|
+
subscribedSessions: /* @__PURE__ */ new Map(),
|
|
47123
|
+
send() {
|
|
47124
|
+
},
|
|
47125
|
+
close() {
|
|
47126
|
+
}
|
|
47127
|
+
};
|
|
47128
|
+
return dispatchRpc(method, body, client, ctx, {
|
|
47129
|
+
handlers,
|
|
47130
|
+
feishuActive: () => feishuActive
|
|
47131
|
+
});
|
|
47132
|
+
}
|
|
47133
|
+
};
|
|
47022
47134
|
const viewerAssetLoader = tryLoadViewerAssets(logger);
|
|
47023
47135
|
const httpRouter = createHttpRouter({
|
|
47024
47136
|
authResolver,
|
|
@@ -47026,6 +47138,7 @@ async function startDaemon(config) {
|
|
|
47026
47138
|
logger,
|
|
47027
47139
|
sessionStore: store,
|
|
47028
47140
|
rpcDispatcher,
|
|
47141
|
+
authedRpc,
|
|
47029
47142
|
...viewerAssetLoader ? { viewerAssetLoader } : {},
|
|
47030
47143
|
// /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
|
|
47031
47144
|
// --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
|
|
@@ -47149,30 +47262,12 @@ async function startDaemon(config) {
|
|
|
47149
47262
|
const wss = wsServer;
|
|
47150
47263
|
wss.onFrame(async (client, frame) => {
|
|
47151
47264
|
const type = frame.type;
|
|
47152
|
-
if (!METHOD_NAMES.includes(type)) {
|
|
47153
|
-
throw new ClawdError(ERROR_CODES.METHOD_NOT_ALLOWED, `unknown method: ${type}`);
|
|
47154
|
-
}
|
|
47155
47265
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
47156
|
-
const handler = handlers[type];
|
|
47157
|
-
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
47158
|
-
if (!feishuActive && FEISHU_GATED_METHODS.includes(type)) {
|
|
47159
|
-
throw new ClawdError(
|
|
47160
|
-
ERROR_CODES.FEISHU_LOGIN_REQUIRED,
|
|
47161
|
-
`${type} requires feishu login (People/remote identity)`
|
|
47162
|
-
);
|
|
47163
|
-
}
|
|
47164
47266
|
const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
|
|
47165
|
-
const
|
|
47166
|
-
|
|
47167
|
-
|
|
47168
|
-
|
|
47169
|
-
throw new ClawdError(
|
|
47170
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
47171
|
-
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${verdict.action} on ${verdict.resource.type}${"id" in verdict.resource ? ":" + verdict.resource.id : ""}`
|
|
47172
|
-
);
|
|
47173
|
-
}
|
|
47174
|
-
}
|
|
47175
|
-
const result = await handler(frame, client, ctx);
|
|
47267
|
+
const result = await dispatchRpc(type, frame, client, ctx, {
|
|
47268
|
+
handlers,
|
|
47269
|
+
feishuActive: () => feishuActive
|
|
47270
|
+
});
|
|
47176
47271
|
if (requestId && result.response) {
|
|
47177
47272
|
client.send({ ...result.response, requestId });
|
|
47178
47273
|
}
|
package/package.json
CHANGED