@clawos-dev/clawd 0.2.253 → 0.2.255
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 +203 -117
- package/dist/dispatch/mcp-server.cjs +22 -4
- package/dist/share-ui/assets/{guest-B7FHhw7e.js → guest-BnunifuX.js} +51 -51
- package/dist/share-ui/guest.html +1 -1
- package/package.json +1 -1
- /package/dist/persona-defaults/{persona-clawd-helper → persona-clawd-butler}/.claude/skills/clawd-config-editor/SKILL.md +0 -0
- /package/dist/persona-defaults/{persona-clawd-helper → persona-clawd-butler}/.claude/skills/clawd-introspection/SKILL.md +0 -0
- /package/dist/persona-defaults/{persona-clawd-helper → persona-clawd-butler}/.claude/skills/clawd-orchestration/SKILL.md +0 -0
- /package/dist/persona-defaults/{persona-clawd-helper → persona-clawd-butler}/.claude/skills/clawd-product/SKILL.md +0 -0
- /package/dist/persona-defaults/{persona-clawd-helper → persona-clawd-butler}/.claude/skills/clawd-session-lens/SKILL.md +0 -0
- /package/dist/persona-defaults/{persona-clawd-helper → persona-clawd-butler}/CLAUDE.md +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -166,6 +166,10 @@ var init_methods = __esm({
|
|
|
166
166
|
// owner UI 授权 contact 反向操作我的设备(跨设备 exec 开关 remoteAccessAllowed)。
|
|
167
167
|
// 详见 protocol/src/contact-remote-access.ts.
|
|
168
168
|
"contact:setRemoteAccess",
|
|
169
|
+
// ---- contact:handshake (双向确认建联握手, spec 2026-07-22) ----
|
|
170
|
+
// device:connect 临时连接调用;被调方同步落库+broadcast 后回 ok(携带身份载荷)。
|
|
171
|
+
// 替代旧 whoami+auto-reverse 组合;whoami 对 connect-token guest 已封(旧客户端拦截)。
|
|
172
|
+
"contact:handshake",
|
|
169
173
|
// ---- visitor:* (web 访客 · persona web 分享,spec 2026-06-24-persona-web-share-design) ----
|
|
170
174
|
// owner-only:列出登录访问过本机 public persona 的 web 访客(零安装、飞书登录、daemon 自签
|
|
171
175
|
// visitor token)。存储 ~/.clawd/visitors.json(VisitorStore),登录(exchange)即 upsert;
|
|
@@ -5367,13 +5371,13 @@ var init_schemas = __esm({
|
|
|
5367
5371
|
* guest 的设备 id / 人 id / 来源全部由 connect token 的 subDevice/subOwner/provider 签名背书
|
|
5368
5372
|
* (server 签、目标设备本地验签,不可伪造),不再接受任何自报身份。
|
|
5369
5373
|
*
|
|
5370
|
-
* selfUrl
|
|
5371
|
-
*
|
|
5374
|
+
* selfUrl 已废弃(spec 2026-07-22 双向确认建联):可达地址自报移入 contact:handshake
|
|
5375
|
+
* args——auth 层回归纯验票。旧客户端发来的 selfUrl 被 strip(zod 默认),无害;
|
|
5376
|
+
* 旧客户端本身由 whoami 的 connect-token 拒绝路径拦截(CLIENT_OUTDATED)。
|
|
5372
5377
|
*/
|
|
5373
|
-
selfUrl: external_exports.string().min(1).optional(),
|
|
5374
5378
|
/**
|
|
5375
5379
|
* 客户端自报的 user-agent 性质标签(诊断/展示用)。
|
|
5376
|
-
*
|
|
5380
|
+
* 「自报事实」,非身份(决策 #16 边界):不参与鉴权、不参与路由、
|
|
5377
5381
|
* 任何 handler 不得据此做权限判断。name 约定命名('clawd-desktop' / 'clawd-web' /
|
|
5378
5382
|
* 'clawd-mobile-android' / ...),宽容未知值(将来加新客户端不动 schema)。
|
|
5379
5383
|
* instanceId:客户端首启生成 UUID 并本地持久化——区分同种类多实例(多手机/多 desktop 同连)。
|
|
@@ -5827,6 +5831,32 @@ var init_contact = __esm({
|
|
|
5827
5831
|
}
|
|
5828
5832
|
});
|
|
5829
5833
|
|
|
5834
|
+
// ../protocol/src/contact-handshake.ts
|
|
5835
|
+
var ContactHandshakeArgsSchema, ContactHandshakeOkSchema;
|
|
5836
|
+
var init_contact_handshake = __esm({
|
|
5837
|
+
"../protocol/src/contact-handshake.ts"() {
|
|
5838
|
+
"use strict";
|
|
5839
|
+
init_zod();
|
|
5840
|
+
init_capability();
|
|
5841
|
+
ContactHandshakeArgsSchema = external_exports.object({
|
|
5842
|
+
selfUrl: external_exports.string().min(1)
|
|
5843
|
+
});
|
|
5844
|
+
ContactHandshakeOkSchema = external_exports.object({
|
|
5845
|
+
type: external_exports.literal("contact:handshake:ok"),
|
|
5846
|
+
owner: external_exports.object({
|
|
5847
|
+
/** 被调方 deviceId(路由主键;调用方落 Contact 的主键用自己请求的 args.deviceId,此值供校验/展示) */
|
|
5848
|
+
id: external_exports.string(),
|
|
5849
|
+
/** 归属人(落 Contact.ownerId / provider,显示分组用) */
|
|
5850
|
+
ownerId: external_exports.string(),
|
|
5851
|
+
provider: external_exports.string(),
|
|
5852
|
+
displayName: external_exports.string()
|
|
5853
|
+
}),
|
|
5854
|
+
/** 被调方授调用方的 grants(落 Contact.grants) */
|
|
5855
|
+
grants: external_exports.array(GrantSchema)
|
|
5856
|
+
});
|
|
5857
|
+
}
|
|
5858
|
+
});
|
|
5859
|
+
|
|
5830
5860
|
// ../protocol/src/contact-remote-access.ts
|
|
5831
5861
|
var ContactSetRemoteAccessArgsSchema, ContactSetRemoteAccessOkSchema, ContactRemoteAccessUpdatedFrameSchema;
|
|
5832
5862
|
var init_contact_remote_access = __esm({
|
|
@@ -6315,6 +6345,7 @@ var init_runtime = __esm({
|
|
|
6315
6345
|
init_capability();
|
|
6316
6346
|
init_inbox();
|
|
6317
6347
|
init_contact();
|
|
6348
|
+
init_contact_handshake();
|
|
6318
6349
|
init_contact_remote_access();
|
|
6319
6350
|
init_peer_exec();
|
|
6320
6351
|
init_extension();
|
|
@@ -45920,7 +45951,12 @@ var DEFAULT_PERSONAS = [
|
|
|
45920
45951
|
sandboxProfile: DEFAULT_BYPASS_PROFILE
|
|
45921
45952
|
},
|
|
45922
45953
|
{
|
|
45923
|
-
|
|
45954
|
+
// clawd 管家 (chief-of-staff):spec 2026-07-22-clawd-butler-persona
|
|
45955
|
+
// 新 personaId 独立于老 persona-clawd-helper(老 helper seed entry 已删;老用户机器上
|
|
45956
|
+
// 若有残留目录,是他们自留物,daemon 不再触碰)。bundle 里 CLAUDE.md 骨架 + 5 个
|
|
45957
|
+
// prebuild skill 走 copyBundleExtras 装到 `.claude/skills/`;skill fetch 用 daemon
|
|
45958
|
+
// 注入的 env CLAWOS_API 拼 `${CLAWOS_API}/api/docs/**` 拉线上手册。
|
|
45959
|
+
personaId: "persona-clawd-butler",
|
|
45924
45960
|
label: "clawd \u7BA1\u5BB6",
|
|
45925
45961
|
model: "opus",
|
|
45926
45962
|
iconKey: "assist",
|
|
@@ -51053,10 +51089,7 @@ var AuthGate = class {
|
|
|
51053
51089
|
st.pending = true;
|
|
51054
51090
|
let r;
|
|
51055
51091
|
try {
|
|
51056
|
-
r = await this.opts.authenticate(
|
|
51057
|
-
parsed.data.token,
|
|
51058
|
-
parsed.data.selfUrl
|
|
51059
|
-
);
|
|
51092
|
+
r = await this.opts.authenticate(parsed.data.token);
|
|
51060
51093
|
} finally {
|
|
51061
51094
|
st.pending = false;
|
|
51062
51095
|
}
|
|
@@ -51160,7 +51193,8 @@ function constantTimeEqual2(a, b2) {
|
|
|
51160
51193
|
function ownerContext(ownerPrincipalId, displayName, feishuUnionId) {
|
|
51161
51194
|
return {
|
|
51162
51195
|
principal: makeOwnerPrincipal(ownerPrincipalId, displayName, feishuUnionId),
|
|
51163
|
-
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
51196
|
+
grants: [{ resource: { type: "*" }, actions: ["admin"] }],
|
|
51197
|
+
tokenKind: "owner"
|
|
51164
51198
|
};
|
|
51165
51199
|
}
|
|
51166
51200
|
function connectGuestContext(subDevice, subOwner, provider, displayName) {
|
|
@@ -51170,6 +51204,7 @@ function connectGuestContext(subDevice, subOwner, provider, displayName) {
|
|
|
51170
51204
|
resource: { ...g2.resource },
|
|
51171
51205
|
actions: [...g2.actions]
|
|
51172
51206
|
})),
|
|
51207
|
+
tokenKind: "connect",
|
|
51173
51208
|
ownerId: subOwner,
|
|
51174
51209
|
provider
|
|
51175
51210
|
};
|
|
@@ -51181,6 +51216,7 @@ function visitorGuestContext(p2) {
|
|
|
51181
51216
|
resource: { ...g2.resource },
|
|
51182
51217
|
actions: [...g2.actions]
|
|
51183
51218
|
})),
|
|
51219
|
+
tokenKind: "visitor",
|
|
51184
51220
|
ownerId: p2.visitorId,
|
|
51185
51221
|
provider: p2.provider
|
|
51186
51222
|
};
|
|
@@ -51529,6 +51565,7 @@ var ContactStore = class {
|
|
|
51529
51565
|
|
|
51530
51566
|
// src/contact/connect-remote.ts
|
|
51531
51567
|
var crypto5 = __toESM(require("crypto"), 1);
|
|
51568
|
+
init_protocol();
|
|
51532
51569
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
51533
51570
|
var RPC_TIMEOUT_MS = 5e3;
|
|
51534
51571
|
async function connectRemote(args) {
|
|
@@ -51541,17 +51578,7 @@ async function connectRemote(args) {
|
|
|
51541
51578
|
});
|
|
51542
51579
|
ws.once("error", onError);
|
|
51543
51580
|
});
|
|
51544
|
-
ws.send(
|
|
51545
|
-
JSON.stringify({
|
|
51546
|
-
type: "auth",
|
|
51547
|
-
token: args.token,
|
|
51548
|
-
// 自动反向(spec 决策 #11):带本机可达地址时对方反向建联系人;
|
|
51549
|
-
// 缺省时省略字段(不发空串),对方 schema selfUrl 是 .min(1).optional()。
|
|
51550
|
-
// 决策 #16:身份字段(selfPrincipalId/selfDisplayName/selfDeviceId)已删——
|
|
51551
|
-
// 对方从 connect token 签名背书取 deviceId/ownerId/provider,不信自报。
|
|
51552
|
-
...args.selfUrl ? { selfUrl: args.selfUrl } : {}
|
|
51553
|
-
})
|
|
51554
|
-
);
|
|
51581
|
+
ws.send(JSON.stringify({ type: "auth", token: args.token }));
|
|
51555
51582
|
await new Promise((resolve6, reject) => {
|
|
51556
51583
|
const onMessage = (raw) => {
|
|
51557
51584
|
let f;
|
|
@@ -51603,7 +51630,7 @@ async function connectRemote(args) {
|
|
|
51603
51630
|
}
|
|
51604
51631
|
if (f.requestId !== requestId) return;
|
|
51605
51632
|
ws.off("message", onMessage);
|
|
51606
|
-
if (f.error || f.type?.endsWith(":error")) {
|
|
51633
|
+
if (f.error || f.type === "error" || f.type?.endsWith(":error")) {
|
|
51607
51634
|
reject(new Error(f.message ?? f.error ?? "rpc error"));
|
|
51608
51635
|
} else {
|
|
51609
51636
|
resolve6(f);
|
|
@@ -51618,13 +51645,14 @@ async function connectRemote(args) {
|
|
|
51618
51645
|
});
|
|
51619
51646
|
}
|
|
51620
51647
|
return {
|
|
51621
|
-
|
|
51622
|
-
const
|
|
51648
|
+
handshake: async (selfUrl) => {
|
|
51649
|
+
const raw = await call("contact:handshake", { selfUrl });
|
|
51650
|
+
const f = ContactHandshakeOkSchema.parse(raw);
|
|
51623
51651
|
return {
|
|
51624
51652
|
ownerId: f.owner.ownerId,
|
|
51625
51653
|
provider: f.owner.provider,
|
|
51626
51654
|
displayName: f.owner.displayName,
|
|
51627
|
-
grants: f.
|
|
51655
|
+
grants: f.grants
|
|
51628
51656
|
};
|
|
51629
51657
|
},
|
|
51630
51658
|
close: () => {
|
|
@@ -51636,43 +51664,6 @@ async function connectRemote(args) {
|
|
|
51636
51664
|
};
|
|
51637
51665
|
}
|
|
51638
51666
|
|
|
51639
|
-
// src/contact/auto-reverse.ts
|
|
51640
|
-
init_protocol();
|
|
51641
|
-
async function autoReverseContact(args) {
|
|
51642
|
-
if (!args.selfUrl) return;
|
|
51643
|
-
const now = args.now ?? Date.now;
|
|
51644
|
-
const grants = PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
51645
|
-
resource: { ...g2.resource },
|
|
51646
|
-
actions: [...g2.actions]
|
|
51647
|
-
}));
|
|
51648
|
-
const existing = args.store.get(args.deviceId) ?? void 0;
|
|
51649
|
-
const base = {
|
|
51650
|
-
deviceId: args.deviceId,
|
|
51651
|
-
ownerId: args.ownerId,
|
|
51652
|
-
provider: args.provider,
|
|
51653
|
-
displayName: args.displayName,
|
|
51654
|
-
remoteUrl: args.selfUrl,
|
|
51655
|
-
connectToken: "",
|
|
51656
|
-
grants,
|
|
51657
|
-
addedAt: now(),
|
|
51658
|
-
pinnedAt: existing?.pinnedAt ?? null,
|
|
51659
|
-
remoteAccessAllowed: existing?.remoteAccessAllowed ?? false
|
|
51660
|
-
};
|
|
51661
|
-
args.store.upsert(base);
|
|
51662
|
-
args.broadcast({ type: "contact:added", contact: base });
|
|
51663
|
-
if (!args.exchangeToken) return;
|
|
51664
|
-
let token;
|
|
51665
|
-
try {
|
|
51666
|
-
token = await args.exchangeToken(args.deviceId);
|
|
51667
|
-
} catch {
|
|
51668
|
-
return;
|
|
51669
|
-
}
|
|
51670
|
-
if (!token) return;
|
|
51671
|
-
const upgraded = { ...base, connectToken: token };
|
|
51672
|
-
args.store.upsert(upgraded);
|
|
51673
|
-
args.broadcast({ type: "contact:added", contact: upgraded });
|
|
51674
|
-
}
|
|
51675
|
-
|
|
51676
51667
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
51677
51668
|
var fs36 = __toESM(require("fs"), 1);
|
|
51678
51669
|
var path37 = __toESM(require("path"), 1);
|
|
@@ -55278,6 +55269,45 @@ function buildInboxHandlers(deps) {
|
|
|
55278
55269
|
|
|
55279
55270
|
// src/handlers/contact.ts
|
|
55280
55271
|
init_protocol();
|
|
55272
|
+
|
|
55273
|
+
// src/contact/auto-reverse.ts
|
|
55274
|
+
init_protocol();
|
|
55275
|
+
function upsertPeerContact(args) {
|
|
55276
|
+
const now = args.now ?? Date.now;
|
|
55277
|
+
const grants = PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
55278
|
+
resource: { ...g2.resource },
|
|
55279
|
+
actions: [...g2.actions]
|
|
55280
|
+
}));
|
|
55281
|
+
const existing = args.store.get(args.deviceId) ?? void 0;
|
|
55282
|
+
const base = {
|
|
55283
|
+
deviceId: args.deviceId,
|
|
55284
|
+
ownerId: args.ownerId,
|
|
55285
|
+
provider: args.provider,
|
|
55286
|
+
displayName: args.displayName,
|
|
55287
|
+
remoteUrl: args.selfUrl,
|
|
55288
|
+
connectToken: "",
|
|
55289
|
+
grants,
|
|
55290
|
+
addedAt: now(),
|
|
55291
|
+
pinnedAt: existing?.pinnedAt ?? null,
|
|
55292
|
+
remoteAccessAllowed: existing?.remoteAccessAllowed ?? false
|
|
55293
|
+
};
|
|
55294
|
+
args.store.upsert(base);
|
|
55295
|
+
args.broadcast({ type: "contact:added", contact: base });
|
|
55296
|
+
return base;
|
|
55297
|
+
}
|
|
55298
|
+
async function backfillConnectToken(args) {
|
|
55299
|
+
try {
|
|
55300
|
+
const token = await args.exchangeToken(args.contact.deviceId);
|
|
55301
|
+
if (!token) return;
|
|
55302
|
+
const upgraded = { ...args.contact, connectToken: token };
|
|
55303
|
+
args.store.upsert(upgraded);
|
|
55304
|
+
args.broadcast({ type: "contact:added", contact: upgraded });
|
|
55305
|
+
} catch {
|
|
55306
|
+
return;
|
|
55307
|
+
}
|
|
55308
|
+
}
|
|
55309
|
+
|
|
55310
|
+
// src/handlers/contact.ts
|
|
55281
55311
|
function ensureOwner(ctx) {
|
|
55282
55312
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
55283
55313
|
throw new ClawdError(
|
|
@@ -55337,10 +55367,49 @@ function buildContactHandlers(deps) {
|
|
|
55337
55367
|
}
|
|
55338
55368
|
};
|
|
55339
55369
|
};
|
|
55370
|
+
const handshake = async (frame, _client, ctx) => {
|
|
55371
|
+
if (!ctx || ctx.tokenKind !== "connect") {
|
|
55372
|
+
throw new ClawdError(
|
|
55373
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
55374
|
+
"contact:handshake \u4EC5\u63A5\u53D7 connect-token \u8FDE\u63A5\uFF08\u8BBE\u5907\u4E92\u8FDE\u63E1\u624B\u4E13\u7528\uFF09"
|
|
55375
|
+
);
|
|
55376
|
+
}
|
|
55377
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
55378
|
+
const args = ContactHandshakeArgsSchema.parse(rest);
|
|
55379
|
+
const contact = upsertPeerContact({
|
|
55380
|
+
store: deps.store,
|
|
55381
|
+
broadcast: deps.broadcast,
|
|
55382
|
+
deviceId: ctx.principal.id,
|
|
55383
|
+
ownerId: ctx.ownerId ?? "",
|
|
55384
|
+
provider: ctx.provider ?? "",
|
|
55385
|
+
displayName: ctx.principal.displayName ?? ctx.principal.id,
|
|
55386
|
+
selfUrl: args.selfUrl,
|
|
55387
|
+
now: deps.now
|
|
55388
|
+
});
|
|
55389
|
+
void backfillConnectToken({
|
|
55390
|
+
store: deps.store,
|
|
55391
|
+
broadcast: deps.broadcast,
|
|
55392
|
+
contact,
|
|
55393
|
+
exchangeToken: deps.exchangeToken
|
|
55394
|
+
});
|
|
55395
|
+
return {
|
|
55396
|
+
response: {
|
|
55397
|
+
type: "contact:handshake:ok",
|
|
55398
|
+
owner: {
|
|
55399
|
+
id: deps.ownerPrincipalId,
|
|
55400
|
+
ownerId: deps.ownerId,
|
|
55401
|
+
provider: deps.ownerProvider,
|
|
55402
|
+
displayName: deps.ownerDisplayName
|
|
55403
|
+
},
|
|
55404
|
+
grants: ctx.grants
|
|
55405
|
+
}
|
|
55406
|
+
};
|
|
55407
|
+
};
|
|
55340
55408
|
return {
|
|
55341
55409
|
"contact:list": list,
|
|
55342
55410
|
"contact:pin": pin,
|
|
55343
|
-
"contact:remove": remove
|
|
55411
|
+
"contact:remove": remove,
|
|
55412
|
+
"contact:handshake": handshake
|
|
55344
55413
|
};
|
|
55345
55414
|
}
|
|
55346
55415
|
|
|
@@ -55393,6 +55462,14 @@ function buildWhoamiHandler(deps) {
|
|
|
55393
55462
|
if (!ctx) {
|
|
55394
55463
|
throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
|
|
55395
55464
|
}
|
|
55465
|
+
if (ctx.tokenKind === "connect") {
|
|
55466
|
+
return {
|
|
55467
|
+
response: {
|
|
55468
|
+
type: "whoami:error",
|
|
55469
|
+
message: "CLIENT_OUTDATED: \u8BF7\u5347\u7EA7\u672C\u673A clawd \u540E\u91CD\u8BD5"
|
|
55470
|
+
}
|
|
55471
|
+
};
|
|
55472
|
+
}
|
|
55396
55473
|
const owner = {
|
|
55397
55474
|
...makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName),
|
|
55398
55475
|
ownerId: deps.ownerId,
|
|
@@ -55499,6 +55576,13 @@ function buildDeviceHandlers(deps) {
|
|
|
55499
55576
|
ensureOwner3(ctx);
|
|
55500
55577
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
55501
55578
|
const args = DeviceConnectArgsSchema.parse(rest);
|
|
55579
|
+
const selfUrl = deps.selfUrl();
|
|
55580
|
+
if (!selfUrl) {
|
|
55581
|
+
throw new ClawdError(
|
|
55582
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
55583
|
+
"TUNNEL_REQUIRED: \u672C\u673A\u672A\u5F00 tunnel\uFF0C\u5F00\u542F\u540E\u91CD\u8BD5"
|
|
55584
|
+
);
|
|
55585
|
+
}
|
|
55502
55586
|
const exchanged = await deps.exchange(args.deviceId);
|
|
55503
55587
|
const url = args.url ?? exchanged.deviceUrl;
|
|
55504
55588
|
if (!url) {
|
|
@@ -55509,14 +55593,7 @@ function buildDeviceHandlers(deps) {
|
|
|
55509
55593
|
}
|
|
55510
55594
|
let conn;
|
|
55511
55595
|
try {
|
|
55512
|
-
conn = await deps.connectRemote({
|
|
55513
|
-
url,
|
|
55514
|
-
token: exchanged.token,
|
|
55515
|
-
// 自动反向(spec 决策 #11):自报本机可达地址,让对方反向加我为联系人;
|
|
55516
|
-
// 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)。
|
|
55517
|
-
// 身份不自报:对方从 token 签名背书取我的 deviceId/ownerId/provider(决策 #16)。
|
|
55518
|
-
selfUrl: deps.selfUrl() ?? void 0
|
|
55519
|
-
});
|
|
55596
|
+
conn = await deps.connectRemote({ url, token: exchanged.token });
|
|
55520
55597
|
} catch (e) {
|
|
55521
55598
|
throw new ClawdError(
|
|
55522
55599
|
ERROR_CODES.VALIDATION_ERROR,
|
|
@@ -55524,21 +55601,33 @@ function buildDeviceHandlers(deps) {
|
|
|
55524
55601
|
);
|
|
55525
55602
|
}
|
|
55526
55603
|
try {
|
|
55527
|
-
|
|
55604
|
+
let hs;
|
|
55605
|
+
try {
|
|
55606
|
+
hs = await conn.handshake(selfUrl);
|
|
55607
|
+
} catch (e) {
|
|
55608
|
+
const msg = e.message;
|
|
55609
|
+
if (/unknown method|METHOD_NOT_ALLOWED/i.test(msg)) {
|
|
55610
|
+
throw new ClawdError(
|
|
55611
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
55612
|
+
"REVERSE_UNSUPPORTED: \u5BF9\u65B9 clawd \u7248\u672C\u8FC7\u65E7\uFF0C\u8BF7\u8BA9\u5BF9\u65B9\u5347\u7EA7\u540E\u91CD\u8BD5"
|
|
55613
|
+
);
|
|
55614
|
+
}
|
|
55615
|
+
throw e;
|
|
55616
|
+
}
|
|
55528
55617
|
const existing = deps.store.get(args.deviceId) ?? void 0;
|
|
55529
55618
|
const contact = {
|
|
55530
55619
|
deviceId: args.deviceId,
|
|
55531
|
-
ownerId:
|
|
55532
|
-
provider:
|
|
55620
|
+
ownerId: hs.ownerId,
|
|
55621
|
+
provider: hs.provider,
|
|
55533
55622
|
// 显示名优先用调用方传的 name:公共目录点击连接带 clawd_devices.name(对外展示名,
|
|
55534
|
-
// 如"公共1");"输入标识"连接不带 name → 回落
|
|
55535
|
-
// 归属人身份不丢(ownerId/provider 仍来自
|
|
55536
|
-
displayName: args.name ||
|
|
55623
|
+
// 如"公共1");"输入标识"连接不带 name → 回落 handshake 报的对方 owner 真名。
|
|
55624
|
+
// 归属人身份不丢(ownerId/provider 仍来自 handshake 背书)。
|
|
55625
|
+
displayName: args.name || hs.displayName || args.deviceId,
|
|
55537
55626
|
remoteUrl: url,
|
|
55538
55627
|
// connectToken 存自包含签名 token(断线重连复用,30 天有效;
|
|
55539
55628
|
// 过期后对方拒 TOKEN_EXPIRED → 重新 device:connect 换新票)
|
|
55540
55629
|
connectToken: exchanged.token,
|
|
55541
|
-
grants:
|
|
55630
|
+
grants: hs.grants,
|
|
55542
55631
|
addedAt: now(),
|
|
55543
55632
|
pinnedAt: existing?.pinnedAt ?? null,
|
|
55544
55633
|
remoteAccessAllowed: existing?.remoteAccessAllowed ?? false
|
|
@@ -57983,7 +58072,12 @@ function buildMethodHandlers(deps) {
|
|
|
57983
58072
|
...buildContactHandlers({
|
|
57984
58073
|
store: deps.contactStore,
|
|
57985
58074
|
broadcast: deps.broadcastToOwners,
|
|
57986
|
-
now: () => Date.now()
|
|
58075
|
+
now: () => Date.now(),
|
|
58076
|
+
ownerPrincipalId: deps.ownerPrincipalId,
|
|
58077
|
+
ownerDisplayName: deps.ownerDisplayName,
|
|
58078
|
+
ownerId: deps.ownerId,
|
|
58079
|
+
ownerProvider: deps.ownerProvider,
|
|
58080
|
+
exchangeToken: deps.exchangeToken
|
|
57987
58081
|
}),
|
|
57988
58082
|
...buildContactRemoteAccessHandlers({
|
|
57989
58083
|
store: deps.contactStore,
|
|
@@ -58329,6 +58423,9 @@ var METHOD_GRANT_MAP = {
|
|
|
58329
58423
|
// contact:setRemoteAccess (owner UI 配远程访问授权):ADMIN_ANY
|
|
58330
58424
|
// (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
|
|
58331
58425
|
"contact:setRemoteAccess": ADMIN_ANY,
|
|
58426
|
+
// contact:handshake: 能连上 = 已通过本地验票;handler 内校验 tokenKind==='connect'
|
|
58427
|
+
// (visitor / owner 不允许触发反向落库)。
|
|
58428
|
+
"contact:handshake": { kind: "public" },
|
|
58332
58429
|
// ---- visitor:* (访客名单,owner-only) ----
|
|
58333
58430
|
// owner 看完整访客名单(含没开会话的);guest 不可调(handler 内再 assertOwner 兜底)。
|
|
58334
58431
|
"visitor:list": ADMIN_ANY,
|
|
@@ -58981,43 +59078,10 @@ async function startDaemon(config) {
|
|
|
58981
59078
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
58982
59079
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 verifyConnectToken (Ed25519).
|
|
58983
59080
|
expectedToken: resolvedAuthToken,
|
|
58984
|
-
|
|
58985
|
-
|
|
58986
|
-
|
|
58987
|
-
|
|
58988
|
-
store: contactStore,
|
|
58989
|
-
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
58990
|
-
deviceId: result.context.principal.id,
|
|
58991
|
-
ownerId: result.context.ownerId ?? "",
|
|
58992
|
-
provider: result.context.provider ?? "",
|
|
58993
|
-
displayName: result.context.principal.displayName ?? result.context.principal.id,
|
|
58994
|
-
selfUrl,
|
|
58995
|
-
// 决策 #16 换票补全:用 owner 当前 jwt 换 aud=对方设备 的票(本机主动连对方用)。
|
|
58996
|
-
// 现读 ownerIdentityStore(热切换后用新身份);失败 → null → 保持空票。
|
|
58997
|
-
exchangeToken: async (targetDeviceId) => {
|
|
58998
|
-
const record = ownerIdentityStore.read();
|
|
58999
|
-
if (!record) return null;
|
|
59000
|
-
try {
|
|
59001
|
-
const r = await centralExchange({
|
|
59002
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
59003
|
-
jwt: record.ttcToken,
|
|
59004
|
-
provider: record.identity.provider,
|
|
59005
|
-
deviceId: targetDeviceId,
|
|
59006
|
-
selfDeviceId: authFile.deviceId
|
|
59007
|
-
});
|
|
59008
|
-
return r.token;
|
|
59009
|
-
} catch (e) {
|
|
59010
|
-
logger.warn("auto-reverse exchange token failed", {
|
|
59011
|
-
targetDeviceId,
|
|
59012
|
-
err: e.message
|
|
59013
|
-
});
|
|
59014
|
-
return null;
|
|
59015
|
-
}
|
|
59016
|
-
}
|
|
59017
|
-
});
|
|
59018
|
-
}
|
|
59019
|
-
return result;
|
|
59020
|
-
},
|
|
59081
|
+
// 纯验票(spec 2026-07-22 双向确认建联):guest 入站 = 自包含 connect token
|
|
59082
|
+
// **本地验签**(Ed25519 公钥 + aud === 本机 deviceId + 未过期),零网络调用。
|
|
59083
|
+
// 反向落库不再挂在验票副作用上——落库唯一路径 = contact:handshake handler。
|
|
59084
|
+
authenticate: async (t) => authenticate(t, buildConnectAuthDeps()),
|
|
59021
59085
|
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
59022
59086
|
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName, ownerFeishuUnionId),
|
|
59023
59087
|
closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
|
|
@@ -59521,8 +59585,30 @@ async function startDaemon(config) {
|
|
|
59521
59585
|
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
59522
59586
|
inboxManager,
|
|
59523
59587
|
inboxStore,
|
|
59524
|
-
// 联系人列表 store(device:connect /
|
|
59588
|
+
// 联系人列表 store(device:connect 出站 / contact:handshake 入站落同一 store)
|
|
59525
59589
|
contactStore,
|
|
59590
|
+
// contact:handshake 换票回填(spec 2026-07-22):用 owner 当前 jwt 换 aud=对方设备 的票。
|
|
59591
|
+
// 现读 ownerIdentityStore(热切换后用新身份);失败 / 未登录 → null → 保持空票。
|
|
59592
|
+
exchangeToken: async (targetDeviceId) => {
|
|
59593
|
+
const record = ownerIdentityStore.read();
|
|
59594
|
+
if (!record) return null;
|
|
59595
|
+
try {
|
|
59596
|
+
const r = await centralExchange({
|
|
59597
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
59598
|
+
jwt: record.ttcToken,
|
|
59599
|
+
provider: record.identity.provider,
|
|
59600
|
+
deviceId: targetDeviceId,
|
|
59601
|
+
selfDeviceId: authFile.deviceId
|
|
59602
|
+
});
|
|
59603
|
+
return r.token;
|
|
59604
|
+
} catch (e) {
|
|
59605
|
+
logger.warn("contact:handshake exchange token failed", {
|
|
59606
|
+
targetDeviceId,
|
|
59607
|
+
err: e.message
|
|
59608
|
+
});
|
|
59609
|
+
return null;
|
|
59610
|
+
}
|
|
59611
|
+
},
|
|
59526
59612
|
// inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
|
|
59527
59613
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
59528
59614
|
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
@@ -22009,13 +22009,13 @@ var AuthRequestFrameSchema = external_exports.object({
|
|
|
22009
22009
|
* guest 的设备 id / 人 id / 来源全部由 connect token 的 subDevice/subOwner/provider 签名背书
|
|
22010
22010
|
* (server 签、目标设备本地验签,不可伪造),不再接受任何自报身份。
|
|
22011
22011
|
*
|
|
22012
|
-
* selfUrl
|
|
22013
|
-
*
|
|
22012
|
+
* selfUrl 已废弃(spec 2026-07-22 双向确认建联):可达地址自报移入 contact:handshake
|
|
22013
|
+
* args——auth 层回归纯验票。旧客户端发来的 selfUrl 被 strip(zod 默认),无害;
|
|
22014
|
+
* 旧客户端本身由 whoami 的 connect-token 拒绝路径拦截(CLIENT_OUTDATED)。
|
|
22014
22015
|
*/
|
|
22015
|
-
selfUrl: external_exports.string().min(1).optional(),
|
|
22016
22016
|
/**
|
|
22017
22017
|
* 客户端自报的 user-agent 性质标签(诊断/展示用)。
|
|
22018
|
-
*
|
|
22018
|
+
* 「自报事实」,非身份(决策 #16 边界):不参与鉴权、不参与路由、
|
|
22019
22019
|
* 任何 handler 不得据此做权限判断。name 约定命名('clawd-desktop' / 'clawd-web' /
|
|
22020
22020
|
* 'clawd-mobile-android' / ...),宽容未知值(将来加新客户端不动 schema)。
|
|
22021
22021
|
* instanceId:客户端首启生成 UUID 并本地持久化——区分同种类多实例(多手机/多 desktop 同连)。
|
|
@@ -22427,6 +22427,24 @@ var ContactPinnedFrameSchema = external_exports.object({
|
|
|
22427
22427
|
pinnedAt: external_exports.number().int().nullable()
|
|
22428
22428
|
}).strict();
|
|
22429
22429
|
|
|
22430
|
+
// ../protocol/src/contact-handshake.ts
|
|
22431
|
+
var ContactHandshakeArgsSchema = external_exports.object({
|
|
22432
|
+
selfUrl: external_exports.string().min(1)
|
|
22433
|
+
});
|
|
22434
|
+
var ContactHandshakeOkSchema = external_exports.object({
|
|
22435
|
+
type: external_exports.literal("contact:handshake:ok"),
|
|
22436
|
+
owner: external_exports.object({
|
|
22437
|
+
/** 被调方 deviceId(路由主键;调用方落 Contact 的主键用自己请求的 args.deviceId,此值供校验/展示) */
|
|
22438
|
+
id: external_exports.string(),
|
|
22439
|
+
/** 归属人(落 Contact.ownerId / provider,显示分组用) */
|
|
22440
|
+
ownerId: external_exports.string(),
|
|
22441
|
+
provider: external_exports.string(),
|
|
22442
|
+
displayName: external_exports.string()
|
|
22443
|
+
}),
|
|
22444
|
+
/** 被调方授调用方的 grants(落 Contact.grants) */
|
|
22445
|
+
grants: external_exports.array(GrantSchema)
|
|
22446
|
+
});
|
|
22447
|
+
|
|
22430
22448
|
// ../protocol/src/contact-remote-access.ts
|
|
22431
22449
|
var ContactSetRemoteAccessArgsSchema = external_exports.object({
|
|
22432
22450
|
deviceId: external_exports.string().min(1),
|