@clawos-dev/clawd 0.2.423 → 0.2.425
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
CHANGED
|
@@ -5974,7 +5974,7 @@ var init_contact_remote_access = __esm({
|
|
|
5974
5974
|
});
|
|
5975
5975
|
|
|
5976
5976
|
// ../protocol/src/contact.ts
|
|
5977
|
-
var ContactSchema, ContactWireSchema, ContactRemoveArgsSchema, ContactRemoveOkSchema, ContactListOkSchema, ContactAddedFrameSchema, ContactRemovedFrameSchema, ContactPinArgsSchema, ContactPinOkSchema, ContactPinnedFrameSchema;
|
|
5977
|
+
var ContactSchema, ContactWireSchema, ContactRemoveArgsSchema, ContactRemoveOkSchema, ContactListOkSchema, ContactAddedFrameSchema, ContactRemovedFrameSchema, ContactPinArgsSchema, ContactSetNoteArgsSchema, ContactSetNoteOkSchema, ContactPinOkSchema, ContactPinnedFrameSchema;
|
|
5978
5978
|
var init_contact = __esm({
|
|
5979
5979
|
"../protocol/src/contact.ts"() {
|
|
5980
5980
|
"use strict";
|
|
@@ -6015,7 +6015,17 @@ var init_contact = __esm({
|
|
|
6015
6015
|
* 跨设备 exec 的总开关(peerExec:run handler 运行时判它)。default=false,owner 显式开。
|
|
6016
6016
|
* 授权粒度 per-people (per-contact):借用别人 persona 本来就带信任对方本人的前提。
|
|
6017
6017
|
*/
|
|
6018
|
-
remoteAccessAllowed: external_exports.boolean().default(false)
|
|
6018
|
+
remoteAccessAllowed: external_exports.boolean().default(false),
|
|
6019
|
+
/**
|
|
6020
|
+
* owner 给这台设备写的备注(2026-08-31)。**我对他那台机器的私记**,不上行、不同步给对方。
|
|
6021
|
+
*
|
|
6022
|
+
* 动机:一人多设备时卡片里只能显示 deviceId(`owner-8bf96a56-6b7e-…` 这种 uuid),
|
|
6023
|
+
* 认不出哪台是哪台。备注让用户能写「老王的 MacBook」。
|
|
6024
|
+
*
|
|
6025
|
+
* 空串 = 没写。default('') 兼容老 contacts.json(无破坏性升级,不 orphan)。
|
|
6026
|
+
* 跟 pinnedAt / remoteAccessAllowed 同类:owner 已配置状态,重复建联 upsert 必须保留。
|
|
6027
|
+
*/
|
|
6028
|
+
note: external_exports.string().default("")
|
|
6019
6029
|
}).strict();
|
|
6020
6030
|
ContactWireSchema = ContactSchema;
|
|
6021
6031
|
ContactRemoveArgsSchema = external_exports.object({
|
|
@@ -6041,6 +6051,16 @@ var init_contact = __esm({
|
|
|
6041
6051
|
deviceId: external_exports.string().min(1),
|
|
6042
6052
|
pinned: external_exports.boolean()
|
|
6043
6053
|
}).strict();
|
|
6054
|
+
ContactSetNoteArgsSchema = external_exports.object({
|
|
6055
|
+
deviceId: external_exports.string().min(1),
|
|
6056
|
+
/** 空串 = 清空备注 */
|
|
6057
|
+
note: external_exports.string()
|
|
6058
|
+
}).strict();
|
|
6059
|
+
ContactSetNoteOkSchema = external_exports.object({
|
|
6060
|
+
type: external_exports.literal("contact:setNote:ok"),
|
|
6061
|
+
deviceId: external_exports.string().min(1),
|
|
6062
|
+
note: external_exports.string()
|
|
6063
|
+
}).strict();
|
|
6044
6064
|
ContactPinOkSchema = external_exports.object({
|
|
6045
6065
|
type: external_exports.literal("contact:pin:ok"),
|
|
6046
6066
|
deviceId: external_exports.string().min(1),
|
|
@@ -8805,6 +8825,10 @@ var init_methods = __esm({
|
|
|
8805
8825
|
summary: "\u7F6E\u9876 / \u53D6\u6D88\u7F6E\u9876\u8054\u7CFB\u4EBA",
|
|
8806
8826
|
args: ContactPinArgsSchema
|
|
8807
8827
|
},
|
|
8828
|
+
"contact:setNote": {
|
|
8829
|
+
summary: "\u7ED9\u8054\u7CFB\u4EBA\uFF08\u5BF9\u65B9\u8BBE\u5907\uFF09\u5199\u5907\u6CE8\u3002owner \u672C\u5730\u79C1\u8BB0\uFF0C\u4E0D\u540C\u6B65\u7ED9\u5BF9\u65B9\uFF1B\u4E00\u4EBA\u591A\u8BBE\u5907\u65F6\u7528\u6765\u533A\u5206\u54EA\u53F0\u662F\u54EA\u53F0",
|
|
8830
|
+
args: ContactSetNoteArgsSchema
|
|
8831
|
+
},
|
|
8808
8832
|
"contact:remove": {
|
|
8809
8833
|
summary: "\u5220\u9664\u8054\u7CFB\u4EBA\u5E76\u6E05\u6389\u5BF9\u5E94\u7684 inbox \u961F\u5217",
|
|
8810
8834
|
args: ContactRemoveArgsSchema
|
|
@@ -54342,8 +54366,9 @@ function routeClaimedEvents(items, opts) {
|
|
|
54342
54366
|
for (const it of items) {
|
|
54343
54367
|
opts.dedupe.add(it.payload.eventId);
|
|
54344
54368
|
const env = { ...it.payload, iat: opts.nowSec, exp: opts.nowSec + 300 };
|
|
54345
|
-
if (it.status === "forwarded" && it.payload.type === "message"
|
|
54346
|
-
|
|
54369
|
+
if (it.status === "forwarded" && (it.payload.type === "message" || it.payload.type === "doc_comment")) {
|
|
54370
|
+
resume.push(env);
|
|
54371
|
+
} else opts.router.handleEnvelope(env);
|
|
54347
54372
|
}
|
|
54348
54373
|
if (resume.length > 0) opts.router.handleResume(resume);
|
|
54349
54374
|
}
|
|
@@ -54406,7 +54431,17 @@ function createLarkChatRouter(deps) {
|
|
|
54406
54431
|
}
|
|
54407
54432
|
return lines;
|
|
54408
54433
|
};
|
|
54434
|
+
const replyAnchorOf = (env) => env.type === "doc_comment" ? env.commentId : env.messageId;
|
|
54435
|
+
const buildDocCommentSection = (env) => {
|
|
54436
|
+
const lines = [];
|
|
54437
|
+
if (env.quote) lines.push(`[\u6587\u6863\u539F\u6587] ${env.quote}`);
|
|
54438
|
+
const body = env.text?.trim();
|
|
54439
|
+
if (body) lines.push(body);
|
|
54440
|
+
if (lines.length === 0) lines.push("[\u6709\u4EBA\u5728\u98DE\u4E66\u6587\u6863\u91CC @ \u4E86\u4F60\uFF0C\u4F46\u8BC4\u8BBA\u5185\u5BB9\u62C9\u53D6\u5931\u8D25]");
|
|
54441
|
+
return lines.join("\n");
|
|
54442
|
+
};
|
|
54409
54443
|
const buildSection = async (env) => {
|
|
54444
|
+
if (env.type === "doc_comment") return buildDocCommentSection(env);
|
|
54410
54445
|
const parentLines = env.parentMessageId ? [
|
|
54411
54446
|
...await imageLinesFor(env, env.parentMessageId, env.parentImageKeys ?? []),
|
|
54412
54447
|
...await fileLinesFor(env, env.parentMessageId, env.parentFiles ?? [])
|
|
@@ -54432,14 +54467,16 @@ ${body}
|
|
|
54432
54467
|
const runBatch = async (batchEnvs, opts) => {
|
|
54433
54468
|
if (deps.isShuttingDown?.()) return;
|
|
54434
54469
|
const envs = batchEnvs.filter((e) => {
|
|
54435
|
-
if (e
|
|
54436
|
-
deps.logger?.warn(`larkBot chat-router:
|
|
54470
|
+
if (replyAnchorOf(e)) return true;
|
|
54471
|
+
deps.logger?.warn(`larkBot chat-router: envelope missing reply anchor, drop ${e.eventId}`);
|
|
54437
54472
|
return false;
|
|
54438
54473
|
});
|
|
54439
54474
|
if (envs.length === 0) return;
|
|
54440
54475
|
const last = envs[envs.length - 1];
|
|
54441
54476
|
const speaker = last.senderName || last.senderOpenId || "\u672A\u77E5\u6210\u5458";
|
|
54442
|
-
const idxByMessageId = new Map(
|
|
54477
|
+
const idxByMessageId = new Map(
|
|
54478
|
+
envs.flatMap((e, i) => e.messageId ? [[e.messageId, i]] : [])
|
|
54479
|
+
);
|
|
54443
54480
|
const sections = [];
|
|
54444
54481
|
for (const [i, env] of envs.entries()) {
|
|
54445
54482
|
const sectionBody = await buildSection(env);
|
|
@@ -54494,9 +54531,12 @@ ${formatLarkSpeakerReminder(speaker)}`
|
|
|
54494
54531
|
await deps.cloud.reply({
|
|
54495
54532
|
appId: last.appId,
|
|
54496
54533
|
chatId: last.chatId,
|
|
54497
|
-
|
|
54534
|
+
// envs 已过滤保证锚点非空
|
|
54535
|
+
replyToMessageId: replyAnchorOf(last),
|
|
54498
54536
|
text: replyText,
|
|
54499
54537
|
eventId: last.eventId,
|
|
54538
|
+
// 文档评论:云端据此走评论回复接口而非 IM(显式字段,不靠 chatId 前缀猜)
|
|
54539
|
+
...last.type === "doc_comment" ? { docComment: { fileToken: last.fileToken, fileType: last.fileType ?? "docx" } } : {},
|
|
54500
54540
|
...absorbed.length > 0 ? { absorbedEventIds: absorbed } : {}
|
|
54501
54541
|
});
|
|
54502
54542
|
return;
|
|
@@ -54520,11 +54560,12 @@ ${formatLarkSpeakerReminder(speaker)}`
|
|
|
54520
54560
|
if (queues.get(chatId) === next) queues.delete(chatId);
|
|
54521
54561
|
});
|
|
54522
54562
|
};
|
|
54523
|
-
const
|
|
54524
|
-
|
|
54563
|
+
const debounceKeyOf = (env) => env.type === "doc_comment" ? `${env.chatId}|${env.commentId ?? ""}` : env.chatId;
|
|
54564
|
+
const flushPending = (key) => {
|
|
54565
|
+
const batch = pending.get(key);
|
|
54525
54566
|
if (!batch) return;
|
|
54526
54567
|
clearTimeout(batch.timer);
|
|
54527
|
-
pending.delete(
|
|
54568
|
+
pending.delete(key);
|
|
54528
54569
|
enqueueBatch(batch.envelopes);
|
|
54529
54570
|
};
|
|
54530
54571
|
return {
|
|
@@ -54542,26 +54583,27 @@ ${formatLarkSpeakerReminder(speaker)}`
|
|
|
54542
54583
|
return;
|
|
54543
54584
|
}
|
|
54544
54585
|
const sender = env.senderOpenId ?? "";
|
|
54545
|
-
const
|
|
54546
|
-
|
|
54547
|
-
|
|
54586
|
+
const key = debounceKeyOf(env);
|
|
54587
|
+
const cur = pending.get(key);
|
|
54588
|
+
if (cur && cur.senderOpenId !== sender) flushPending(key);
|
|
54589
|
+
const batch = pending.get(key);
|
|
54548
54590
|
if (batch) {
|
|
54549
54591
|
batch.envelopes.push(env);
|
|
54550
54592
|
clearTimeout(batch.timer);
|
|
54551
|
-
batch.timer = setTimeout(() => flushPending(
|
|
54593
|
+
batch.timer = setTimeout(() => flushPending(key), debounceMs);
|
|
54552
54594
|
} else {
|
|
54553
|
-
pending.set(
|
|
54595
|
+
pending.set(key, {
|
|
54554
54596
|
envelopes: [env],
|
|
54555
54597
|
senderOpenId: sender,
|
|
54556
|
-
timer: setTimeout(() => flushPending(
|
|
54598
|
+
timer: setTimeout(() => flushPending(key), debounceMs)
|
|
54557
54599
|
});
|
|
54558
54600
|
}
|
|
54559
54601
|
},
|
|
54560
54602
|
handleResume(envelopes) {
|
|
54561
54603
|
const groups = /* @__PURE__ */ new Map();
|
|
54562
54604
|
for (const env of envelopes) {
|
|
54563
|
-
if (env.type !== "message") continue;
|
|
54564
|
-
const key = `${env
|
|
54605
|
+
if (env.type !== "message" && env.type !== "doc_comment") continue;
|
|
54606
|
+
const key = `${debounceKeyOf(env)}|${env.senderOpenId ?? ""}`;
|
|
54565
54607
|
const group = groups.get(key);
|
|
54566
54608
|
if (group) group.push(env);
|
|
54567
54609
|
else groups.set(key, [env]);
|
|
@@ -57394,6 +57436,18 @@ var ContactStore = class {
|
|
|
57394
57436
|
this.flush();
|
|
57395
57437
|
return true;
|
|
57396
57438
|
}
|
|
57439
|
+
/**
|
|
57440
|
+
* 更新单条 contact 的备注(2026-08-31,对齐 setPin pattern)。
|
|
57441
|
+
* @param note 空串 = 清空备注
|
|
57442
|
+
* @returns 是否命中:deviceId 不存在返 false;命中即 flush.
|
|
57443
|
+
*/
|
|
57444
|
+
setNote(deviceId, note) {
|
|
57445
|
+
const existing = this.contacts.get(deviceId);
|
|
57446
|
+
if (!existing) return false;
|
|
57447
|
+
this.contacts.set(deviceId, { ...existing, note });
|
|
57448
|
+
this.flush();
|
|
57449
|
+
return true;
|
|
57450
|
+
}
|
|
57397
57451
|
flush() {
|
|
57398
57452
|
const file = path45.join(this.dataDir, FILE_NAME);
|
|
57399
57453
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -59733,6 +59787,11 @@ var LoginFlow = class {
|
|
|
59733
59787
|
ttcTokenExpiresAt: Number.isFinite(expiresAtNum) ? expiresAtNum : null,
|
|
59734
59788
|
loginAt: new Date(now).toISOString()
|
|
59735
59789
|
});
|
|
59790
|
+
void this.deps.syncOrgMesh?.().catch((err) => {
|
|
59791
|
+
this.deps.logWarn?.("org-mesh: \u767B\u5F55\u540E\u540C\u6B65\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u767B\u5F55\uFF09", {
|
|
59792
|
+
error: err instanceof Error ? err.message : String(err)
|
|
59793
|
+
});
|
|
59794
|
+
});
|
|
59736
59795
|
return { ok: true, identity };
|
|
59737
59796
|
}
|
|
59738
59797
|
gcExpired(now) {
|
|
@@ -59956,6 +60015,219 @@ async function upsertDeviceBinding(opts) {
|
|
|
59956
60015
|
}
|
|
59957
60016
|
}
|
|
59958
60017
|
|
|
60018
|
+
// src/contact/org-mesh.ts
|
|
60019
|
+
var OrgMeshClientError = class extends Error {
|
|
60020
|
+
constructor(code, message, cause) {
|
|
60021
|
+
super(message);
|
|
60022
|
+
this.code = code;
|
|
60023
|
+
this.cause = cause;
|
|
60024
|
+
this.name = "OrgMeshClientError";
|
|
60025
|
+
}
|
|
60026
|
+
code;
|
|
60027
|
+
cause;
|
|
60028
|
+
};
|
|
60029
|
+
var DEFAULT_TIMEOUT_MS4 = 8e3;
|
|
60030
|
+
async function fetchOrgPeers(opts) {
|
|
60031
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
60032
|
+
const ac = new AbortController();
|
|
60033
|
+
const timer = setTimeout(() => ac.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS4);
|
|
60034
|
+
let res;
|
|
60035
|
+
try {
|
|
60036
|
+
res = await doFetch(`${opts.apiUrl}/api/device-bindings/org-peers`, {
|
|
60037
|
+
method: "GET",
|
|
60038
|
+
signal: ac.signal,
|
|
60039
|
+
headers: {
|
|
60040
|
+
authorization: `Bearer ${opts.apiKey}`,
|
|
60041
|
+
"x-did": encodeURIComponent(opts.deviceId),
|
|
60042
|
+
"x-name": encodeURIComponent(opts.displayName),
|
|
60043
|
+
"x-scene": "daemon"
|
|
60044
|
+
}
|
|
60045
|
+
});
|
|
60046
|
+
} catch (err) {
|
|
60047
|
+
throw new OrgMeshClientError("NETWORK", `network error: ${err.message}`, err);
|
|
60048
|
+
} finally {
|
|
60049
|
+
clearTimeout(timer);
|
|
60050
|
+
}
|
|
60051
|
+
if (!res.ok) {
|
|
60052
|
+
let detail = "";
|
|
60053
|
+
try {
|
|
60054
|
+
detail = JSON.stringify(await res.json());
|
|
60055
|
+
} catch {
|
|
60056
|
+
}
|
|
60057
|
+
throw new OrgMeshClientError("API_ERROR", `org-peers failed: ${res.status} ${detail}`);
|
|
60058
|
+
}
|
|
60059
|
+
const body = await res.json();
|
|
60060
|
+
return body.items ?? [];
|
|
60061
|
+
}
|
|
60062
|
+
async function syncOrgMesh(deps) {
|
|
60063
|
+
const peers = await deps.fetchPeers();
|
|
60064
|
+
if (peers.length === 0) {
|
|
60065
|
+
deps.log("org-mesh: \u4E0D\u5728\u4EFB\u4F55\u7EC4\u7EC7\uFF08\u6216\u7EC4\u91CC\u53EA\u6709\u81EA\u5DF1\uFF09\uFF0C\u8DF3\u8FC7");
|
|
60066
|
+
return { peers: 0, attempted: 0, connected: 0, failed: 0 };
|
|
60067
|
+
}
|
|
60068
|
+
const targets = [];
|
|
60069
|
+
for (const p2 of peers) {
|
|
60070
|
+
for (const d of p2.devices) {
|
|
60071
|
+
if (deps.store.get(d.deviceId)) continue;
|
|
60072
|
+
targets.push({ deviceId: d.deviceId, displayName: p2.displayName });
|
|
60073
|
+
}
|
|
60074
|
+
}
|
|
60075
|
+
deps.log("org-mesh: \u5F00\u59CB\u540C\u6B65", { peers: peers.length, targets: targets.length });
|
|
60076
|
+
let connected = 0;
|
|
60077
|
+
let failed = 0;
|
|
60078
|
+
for (const t of targets) {
|
|
60079
|
+
try {
|
|
60080
|
+
await deps.connectToDevice({ deviceId: t.deviceId, name: t.displayName });
|
|
60081
|
+
connected += 1;
|
|
60082
|
+
deps.log("org-mesh: \u5DF2\u5EFA\u8054", { deviceId: t.deviceId, name: t.displayName });
|
|
60083
|
+
} catch (e) {
|
|
60084
|
+
failed += 1;
|
|
60085
|
+
deps.log("org-mesh: \u5EFA\u8054\u5931\u8D25\uFF0C\u8DF3\u8FC7", {
|
|
60086
|
+
deviceId: t.deviceId,
|
|
60087
|
+
name: t.displayName,
|
|
60088
|
+
reason: e.message
|
|
60089
|
+
});
|
|
60090
|
+
}
|
|
60091
|
+
}
|
|
60092
|
+
deps.log("org-mesh: \u540C\u6B65\u5B8C\u6210", { peers: peers.length, attempted: targets.length, connected, failed });
|
|
60093
|
+
return { peers: peers.length, attempted: targets.length, connected, failed };
|
|
60094
|
+
}
|
|
60095
|
+
|
|
60096
|
+
// src/handlers/device.ts
|
|
60097
|
+
init_protocol();
|
|
60098
|
+
function ensureOwner(ctx) {
|
|
60099
|
+
if (!ctx || ctx.principal.kind !== "owner") {
|
|
60100
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
|
|
60101
|
+
}
|
|
60102
|
+
}
|
|
60103
|
+
function rethrowLoginExpired(e) {
|
|
60104
|
+
if (e instanceof CentralClientError && e.code === "UNAUTHORIZED") {
|
|
60105
|
+
throw new ClawdError(
|
|
60106
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
60107
|
+
`${LOGIN_EXPIRED_ERROR_MARKER}: \u767B\u5F55\u51ED\u8BC1\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55`
|
|
60108
|
+
);
|
|
60109
|
+
}
|
|
60110
|
+
throw e;
|
|
60111
|
+
}
|
|
60112
|
+
function createConnectToDevice(deps) {
|
|
60113
|
+
const now = deps.now ?? Date.now;
|
|
60114
|
+
return async (args) => {
|
|
60115
|
+
const selfUrl = deps.selfUrl();
|
|
60116
|
+
if (!selfUrl) {
|
|
60117
|
+
throw new ClawdError(
|
|
60118
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
60119
|
+
"TUNNEL_REQUIRED: \u672C\u673A\u672A\u5F00 tunnel\uFF0C\u5F00\u542F\u540E\u91CD\u8BD5"
|
|
60120
|
+
);
|
|
60121
|
+
}
|
|
60122
|
+
const exchanged = await deps.exchange(args.deviceId).catch(rethrowLoginExpired);
|
|
60123
|
+
const url = args.url ?? exchanged.deviceUrl;
|
|
60124
|
+
if (!url) {
|
|
60125
|
+
throw new ClawdError(
|
|
60126
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
60127
|
+
"DEVICE_URL_UNKNOWN: \u5BF9\u65B9\u8BBE\u5907\u672A\u4E0A\u62A5\u5730\u5740\uFF08\u672A\u767B\u5F55\u6216\u672A\u5F00 tunnel\uFF09\uFF0C\u8BF7\u663E\u5F0F\u63D0\u4F9B url \u6216\u7B49\u5BF9\u65B9\u4E0A\u7EBF"
|
|
60128
|
+
);
|
|
60129
|
+
}
|
|
60130
|
+
let conn;
|
|
60131
|
+
try {
|
|
60132
|
+
conn = await deps.connectRemote({ url, token: exchanged.token });
|
|
60133
|
+
} catch (e) {
|
|
60134
|
+
throw new ClawdError(
|
|
60135
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
60136
|
+
`DEVICE_UNREACHABLE: ${e.message}`
|
|
60137
|
+
);
|
|
60138
|
+
}
|
|
60139
|
+
try {
|
|
60140
|
+
let hs;
|
|
60141
|
+
try {
|
|
60142
|
+
hs = await conn.handshake(selfUrl);
|
|
60143
|
+
} catch (e) {
|
|
60144
|
+
const msg = e.message;
|
|
60145
|
+
if (/unknown method|METHOD_NOT_ALLOWED/i.test(msg)) {
|
|
60146
|
+
throw new ClawdError(
|
|
60147
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
60148
|
+
"REVERSE_UNSUPPORTED: \u5BF9\u65B9 clawd \u7248\u672C\u8FC7\u65E7\uFF0C\u8BF7\u8BA9\u5BF9\u65B9\u5347\u7EA7\u540E\u91CD\u8BD5"
|
|
60149
|
+
);
|
|
60150
|
+
}
|
|
60151
|
+
if (msg.includes(PEER_LOGIN_EXPIRED_ERROR_MARKER)) {
|
|
60152
|
+
throw new ClawdError(
|
|
60153
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
60154
|
+
`${PEER_LOGIN_EXPIRED_ERROR_MARKER}: \u5BF9\u65B9\u8BBE\u5907\u767B\u5F55\u5DF2\u8FC7\u671F\u6216\u672A\u767B\u5F55\uFF0C\u8BF7\u5BF9\u65B9\u5728 clawd \u91CD\u65B0\u767B\u5F55\u540E\u518D\u8BD5`
|
|
60155
|
+
);
|
|
60156
|
+
}
|
|
60157
|
+
throw e;
|
|
60158
|
+
}
|
|
60159
|
+
const existing = deps.store.get(args.deviceId) ?? void 0;
|
|
60160
|
+
const contact = {
|
|
60161
|
+
deviceId: args.deviceId,
|
|
60162
|
+
ownerId: hs.ownerId,
|
|
60163
|
+
provider: hs.provider,
|
|
60164
|
+
// 显示名优先用调用方传的 name:公共目录点击连接带 clawd_devices.name(对外展示名,
|
|
60165
|
+
// 如"公共1");"输入标识"连接不带 name → 回落 handshake 报的对方 owner 真名。
|
|
60166
|
+
// 归属人身份不丢(ownerId/provider 仍来自 handshake 背书)。
|
|
60167
|
+
displayName: args.name || hs.displayName || args.deviceId,
|
|
60168
|
+
remoteUrl: url,
|
|
60169
|
+
// connectToken 存自包含签名 token(断线重连复用,30 天有效;
|
|
60170
|
+
// 过期后对方拒 TOKEN_EXPIRED → 重新 device:connect 换新票)
|
|
60171
|
+
connectToken: exchanged.token,
|
|
60172
|
+
grants: hs.grants,
|
|
60173
|
+
addedAt: now(),
|
|
60174
|
+
pinnedAt: existing?.pinnedAt ?? null,
|
|
60175
|
+
remoteAccessAllowed: existing?.remoteAccessAllowed ?? false,
|
|
60176
|
+
// 备注是 owner 已配置状态,重复建联 upsert 必须保留(同 pinnedAt / remoteAccessAllowed)
|
|
60177
|
+
note: existing?.note ?? ""
|
|
60178
|
+
};
|
|
60179
|
+
deps.store.upsert(contact);
|
|
60180
|
+
deps.broadcast({ type: "contact:added", contact });
|
|
60181
|
+
return contact;
|
|
60182
|
+
} finally {
|
|
60183
|
+
try {
|
|
60184
|
+
conn.close();
|
|
60185
|
+
} catch {
|
|
60186
|
+
}
|
|
60187
|
+
}
|
|
60188
|
+
};
|
|
60189
|
+
}
|
|
60190
|
+
function buildDeviceHandlers(deps) {
|
|
60191
|
+
const connectToDevice = createConnectToDevice(deps);
|
|
60192
|
+
const list = async (_frame, _client, ctx) => {
|
|
60193
|
+
ensureOwner(ctx);
|
|
60194
|
+
const devices = await deps.listDevices().catch(rethrowLoginExpired);
|
|
60195
|
+
return {
|
|
60196
|
+
response: { type: "device:list:ok", devices }
|
|
60197
|
+
};
|
|
60198
|
+
};
|
|
60199
|
+
const connect = async (frame, _client, ctx) => {
|
|
60200
|
+
ensureOwner(ctx);
|
|
60201
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
60202
|
+
const args = DeviceConnectArgsSchema.parse(rest);
|
|
60203
|
+
const contact = await connectToDevice(args);
|
|
60204
|
+
return {
|
|
60205
|
+
response: {
|
|
60206
|
+
type: "device:connect:ok",
|
|
60207
|
+
// 返回连接的设备标识(= 请求的 args.deviceId)
|
|
60208
|
+
deviceId: args.deviceId,
|
|
60209
|
+
name: contact.displayName,
|
|
60210
|
+
url: contact.remoteUrl
|
|
60211
|
+
}
|
|
60212
|
+
};
|
|
60213
|
+
};
|
|
60214
|
+
const connections = async (_frame, client, ctx) => {
|
|
60215
|
+
ensureOwner(ctx);
|
|
60216
|
+
const entries = deps.listOwnerConnections();
|
|
60217
|
+
return {
|
|
60218
|
+
response: {
|
|
60219
|
+
type: "device:connections:ok",
|
|
60220
|
+
connections: entries.map((e) => ({ ...e, self: e.clientId === client.id }))
|
|
60221
|
+
}
|
|
60222
|
+
};
|
|
60223
|
+
};
|
|
60224
|
+
return {
|
|
60225
|
+
"device:list": list,
|
|
60226
|
+
"device:connect": connect,
|
|
60227
|
+
"device:connections": connections
|
|
60228
|
+
};
|
|
60229
|
+
}
|
|
60230
|
+
|
|
59959
60231
|
// src/feishu-auth/verify-token.ts
|
|
59960
60232
|
var crypto11 = __toESM(require("crypto"), 1);
|
|
59961
60233
|
var CONNECT_TOKEN_PREFIX = "clawdtk1";
|
|
@@ -61637,7 +61909,9 @@ function upsertPeerContact(args) {
|
|
|
61637
61909
|
grants,
|
|
61638
61910
|
addedAt: now(),
|
|
61639
61911
|
pinnedAt: existing?.pinnedAt ?? null,
|
|
61640
|
-
remoteAccessAllowed: existing?.remoteAccessAllowed ?? false
|
|
61912
|
+
remoteAccessAllowed: existing?.remoteAccessAllowed ?? false,
|
|
61913
|
+
// 备注是 owner 已配置状态,重复建联 upsert 必须保留(同 pinnedAt / remoteAccessAllowed)
|
|
61914
|
+
note: existing?.note ?? ""
|
|
61641
61915
|
};
|
|
61642
61916
|
args.store.upsert(base);
|
|
61643
61917
|
args.broadcast({ type: "contact:added", contact: base });
|
|
@@ -61645,7 +61919,7 @@ function upsertPeerContact(args) {
|
|
|
61645
61919
|
}
|
|
61646
61920
|
|
|
61647
61921
|
// src/handlers/contact.ts
|
|
61648
|
-
function
|
|
61922
|
+
function ensureOwner2(ctx) {
|
|
61649
61923
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
61650
61924
|
throw new ClawdError(
|
|
61651
61925
|
ERROR_CODES.UNAUTHORIZED,
|
|
@@ -61655,7 +61929,7 @@ function ensureOwner(ctx) {
|
|
|
61655
61929
|
}
|
|
61656
61930
|
function buildContactHandlers(deps) {
|
|
61657
61931
|
const list = async (_frame, _client, ctx) => {
|
|
61658
|
-
|
|
61932
|
+
ensureOwner2(ctx);
|
|
61659
61933
|
return {
|
|
61660
61934
|
response: {
|
|
61661
61935
|
type: "contact:list:ok",
|
|
@@ -61664,7 +61938,7 @@ function buildContactHandlers(deps) {
|
|
|
61664
61938
|
};
|
|
61665
61939
|
};
|
|
61666
61940
|
const pin = async (frame, _client, ctx) => {
|
|
61667
|
-
|
|
61941
|
+
ensureOwner2(ctx);
|
|
61668
61942
|
const args = parseArgs2(ContactPinArgsSchema, frame, "contact:pin");
|
|
61669
61943
|
const pinnedAt = args.pinned ? deps.now() : null;
|
|
61670
61944
|
const hit = deps.store.setPin(args.deviceId, pinnedAt);
|
|
@@ -61687,8 +61961,31 @@ function buildContactHandlers(deps) {
|
|
|
61687
61961
|
}
|
|
61688
61962
|
};
|
|
61689
61963
|
};
|
|
61964
|
+
const setNote = async (frame, _client, ctx) => {
|
|
61965
|
+
ensureOwner2(ctx);
|
|
61966
|
+
const args = parseArgs2(ContactSetNoteArgsSchema, frame, "contact:setNote");
|
|
61967
|
+
const hit = deps.store.setNote(args.deviceId, args.note);
|
|
61968
|
+
if (!hit) {
|
|
61969
|
+
throw new ClawdError(
|
|
61970
|
+
ERROR_CODES.CONTACT_NOT_FOUND,
|
|
61971
|
+
`CONTACT_NOT_FOUND: contact ${args.deviceId} not in store`
|
|
61972
|
+
);
|
|
61973
|
+
}
|
|
61974
|
+
deps.broadcast({
|
|
61975
|
+
type: "contact:note-updated",
|
|
61976
|
+
deviceId: args.deviceId,
|
|
61977
|
+
note: args.note
|
|
61978
|
+
});
|
|
61979
|
+
return {
|
|
61980
|
+
response: {
|
|
61981
|
+
type: "contact:setNote:ok",
|
|
61982
|
+
deviceId: args.deviceId,
|
|
61983
|
+
note: args.note
|
|
61984
|
+
}
|
|
61985
|
+
};
|
|
61986
|
+
};
|
|
61690
61987
|
const remove = async (frame, _client, ctx) => {
|
|
61691
|
-
|
|
61988
|
+
ensureOwner2(ctx);
|
|
61692
61989
|
const args = parseArgs2(ContactRemoveArgsSchema, frame, "contact:remove");
|
|
61693
61990
|
deps.store.removeByDeviceId(args.deviceId);
|
|
61694
61991
|
deps.broadcast({
|
|
@@ -61753,6 +62050,7 @@ function buildContactHandlers(deps) {
|
|
|
61753
62050
|
return {
|
|
61754
62051
|
"contact:list": list,
|
|
61755
62052
|
"contact:pin": pin,
|
|
62053
|
+
"contact:setNote": setNote,
|
|
61756
62054
|
"contact:remove": remove,
|
|
61757
62055
|
"contact:handshake": handshake
|
|
61758
62056
|
};
|
|
@@ -61760,7 +62058,7 @@ function buildContactHandlers(deps) {
|
|
|
61760
62058
|
|
|
61761
62059
|
// src/handlers/contact-remote-access.ts
|
|
61762
62060
|
init_protocol();
|
|
61763
|
-
function
|
|
62061
|
+
function ensureOwner3(ctx) {
|
|
61764
62062
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
61765
62063
|
throw new ClawdError(
|
|
61766
62064
|
ERROR_CODES.UNAUTHORIZED,
|
|
@@ -61770,7 +62068,7 @@ function ensureOwner2(ctx) {
|
|
|
61770
62068
|
}
|
|
61771
62069
|
function buildContactRemoteAccessHandlers(deps) {
|
|
61772
62070
|
const setRemoteAccess = async (frame, _client, ctx) => {
|
|
61773
|
-
|
|
62071
|
+
ensureOwner3(ctx);
|
|
61774
62072
|
const args = parseArgs2(ContactSetRemoteAccessArgsSchema, frame, "contact:setRemoteAccess");
|
|
61775
62073
|
const hit = deps.store.setRemoteAccess(args.deviceId, {
|
|
61776
62074
|
remoteAccessAllowed: args.remoteAccessAllowed
|
|
@@ -61903,132 +62201,6 @@ function buildFeishuAuthHandlers(deps) {
|
|
|
61903
62201
|
};
|
|
61904
62202
|
}
|
|
61905
62203
|
|
|
61906
|
-
// src/handlers/device.ts
|
|
61907
|
-
init_protocol();
|
|
61908
|
-
function ensureOwner3(ctx) {
|
|
61909
|
-
if (!ctx || ctx.principal.kind !== "owner") {
|
|
61910
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
|
|
61911
|
-
}
|
|
61912
|
-
}
|
|
61913
|
-
function rethrowLoginExpired(e) {
|
|
61914
|
-
if (e instanceof CentralClientError && e.code === "UNAUTHORIZED") {
|
|
61915
|
-
throw new ClawdError(
|
|
61916
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
61917
|
-
`${LOGIN_EXPIRED_ERROR_MARKER}: \u767B\u5F55\u51ED\u8BC1\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55`
|
|
61918
|
-
);
|
|
61919
|
-
}
|
|
61920
|
-
throw e;
|
|
61921
|
-
}
|
|
61922
|
-
function buildDeviceHandlers(deps) {
|
|
61923
|
-
const now = deps.now ?? Date.now;
|
|
61924
|
-
const list = async (_frame, _client, ctx) => {
|
|
61925
|
-
ensureOwner3(ctx);
|
|
61926
|
-
const devices = await deps.listDevices().catch(rethrowLoginExpired);
|
|
61927
|
-
return {
|
|
61928
|
-
response: { type: "device:list:ok", devices }
|
|
61929
|
-
};
|
|
61930
|
-
};
|
|
61931
|
-
const connect = async (frame, _client, ctx) => {
|
|
61932
|
-
ensureOwner3(ctx);
|
|
61933
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
61934
|
-
const args = DeviceConnectArgsSchema.parse(rest);
|
|
61935
|
-
const selfUrl = deps.selfUrl();
|
|
61936
|
-
if (!selfUrl) {
|
|
61937
|
-
throw new ClawdError(
|
|
61938
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
61939
|
-
"TUNNEL_REQUIRED: \u672C\u673A\u672A\u5F00 tunnel\uFF0C\u5F00\u542F\u540E\u91CD\u8BD5"
|
|
61940
|
-
);
|
|
61941
|
-
}
|
|
61942
|
-
const exchanged = await deps.exchange(args.deviceId).catch(rethrowLoginExpired);
|
|
61943
|
-
const url = args.url ?? exchanged.deviceUrl;
|
|
61944
|
-
if (!url) {
|
|
61945
|
-
throw new ClawdError(
|
|
61946
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
61947
|
-
"DEVICE_URL_UNKNOWN: \u5BF9\u65B9\u8BBE\u5907\u672A\u4E0A\u62A5\u5730\u5740\uFF08\u672A\u767B\u5F55\u6216\u672A\u5F00 tunnel\uFF09\uFF0C\u8BF7\u663E\u5F0F\u63D0\u4F9B url \u6216\u7B49\u5BF9\u65B9\u4E0A\u7EBF"
|
|
61948
|
-
);
|
|
61949
|
-
}
|
|
61950
|
-
let conn;
|
|
61951
|
-
try {
|
|
61952
|
-
conn = await deps.connectRemote({ url, token: exchanged.token });
|
|
61953
|
-
} catch (e) {
|
|
61954
|
-
throw new ClawdError(
|
|
61955
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
61956
|
-
`DEVICE_UNREACHABLE: ${e.message}`
|
|
61957
|
-
);
|
|
61958
|
-
}
|
|
61959
|
-
try {
|
|
61960
|
-
let hs;
|
|
61961
|
-
try {
|
|
61962
|
-
hs = await conn.handshake(selfUrl);
|
|
61963
|
-
} catch (e) {
|
|
61964
|
-
const msg = e.message;
|
|
61965
|
-
if (/unknown method|METHOD_NOT_ALLOWED/i.test(msg)) {
|
|
61966
|
-
throw new ClawdError(
|
|
61967
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
61968
|
-
"REVERSE_UNSUPPORTED: \u5BF9\u65B9 clawd \u7248\u672C\u8FC7\u65E7\uFF0C\u8BF7\u8BA9\u5BF9\u65B9\u5347\u7EA7\u540E\u91CD\u8BD5"
|
|
61969
|
-
);
|
|
61970
|
-
}
|
|
61971
|
-
if (msg.includes(PEER_LOGIN_EXPIRED_ERROR_MARKER)) {
|
|
61972
|
-
throw new ClawdError(
|
|
61973
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
61974
|
-
`${PEER_LOGIN_EXPIRED_ERROR_MARKER}: \u5BF9\u65B9\u8BBE\u5907\u767B\u5F55\u5DF2\u8FC7\u671F\u6216\u672A\u767B\u5F55\uFF0C\u8BF7\u5BF9\u65B9\u5728 clawd \u91CD\u65B0\u767B\u5F55\u540E\u518D\u8BD5`
|
|
61975
|
-
);
|
|
61976
|
-
}
|
|
61977
|
-
throw e;
|
|
61978
|
-
}
|
|
61979
|
-
const existing = deps.store.get(args.deviceId) ?? void 0;
|
|
61980
|
-
const contact = {
|
|
61981
|
-
deviceId: args.deviceId,
|
|
61982
|
-
ownerId: hs.ownerId,
|
|
61983
|
-
provider: hs.provider,
|
|
61984
|
-
// 显示名优先用调用方传的 name:公共目录点击连接带 clawd_devices.name(对外展示名,
|
|
61985
|
-
// 如"公共1");"输入标识"连接不带 name → 回落 handshake 报的对方 owner 真名。
|
|
61986
|
-
// 归属人身份不丢(ownerId/provider 仍来自 handshake 背书)。
|
|
61987
|
-
displayName: args.name || hs.displayName || args.deviceId,
|
|
61988
|
-
remoteUrl: url,
|
|
61989
|
-
// connectToken 存自包含签名 token(断线重连复用,30 天有效;
|
|
61990
|
-
// 过期后对方拒 TOKEN_EXPIRED → 重新 device:connect 换新票)
|
|
61991
|
-
connectToken: exchanged.token,
|
|
61992
|
-
grants: hs.grants,
|
|
61993
|
-
addedAt: now(),
|
|
61994
|
-
pinnedAt: existing?.pinnedAt ?? null,
|
|
61995
|
-
remoteAccessAllowed: existing?.remoteAccessAllowed ?? false
|
|
61996
|
-
};
|
|
61997
|
-
deps.store.upsert(contact);
|
|
61998
|
-
deps.broadcast({ type: "contact:added", contact });
|
|
61999
|
-
return {
|
|
62000
|
-
response: {
|
|
62001
|
-
type: "device:connect:ok",
|
|
62002
|
-
// 返回连接的设备标识(= 请求的 args.deviceId)
|
|
62003
|
-
deviceId: args.deviceId,
|
|
62004
|
-
name: contact.displayName,
|
|
62005
|
-
url
|
|
62006
|
-
}
|
|
62007
|
-
};
|
|
62008
|
-
} finally {
|
|
62009
|
-
try {
|
|
62010
|
-
conn.close();
|
|
62011
|
-
} catch {
|
|
62012
|
-
}
|
|
62013
|
-
}
|
|
62014
|
-
};
|
|
62015
|
-
const connections = async (_frame, client, ctx) => {
|
|
62016
|
-
ensureOwner3(ctx);
|
|
62017
|
-
const entries = deps.listOwnerConnections();
|
|
62018
|
-
return {
|
|
62019
|
-
response: {
|
|
62020
|
-
type: "device:connections:ok",
|
|
62021
|
-
connections: entries.map((e) => ({ ...e, self: e.clientId === client.id }))
|
|
62022
|
-
}
|
|
62023
|
-
};
|
|
62024
|
-
};
|
|
62025
|
-
return {
|
|
62026
|
-
"device:list": list,
|
|
62027
|
-
"device:connect": connect,
|
|
62028
|
-
"device:connections": connections
|
|
62029
|
-
};
|
|
62030
|
-
}
|
|
62031
|
-
|
|
62032
62204
|
// src/handlers/meta.ts
|
|
62033
62205
|
var import_node_os19 = __toESM(require("os"), 1);
|
|
62034
62206
|
init_protocol();
|
|
@@ -62063,6 +62235,7 @@ var METHOD_GRANT_MAP = {
|
|
|
62063
62235
|
// 写入路径在 device:connect / 自动反向;list / remove 作联系人读 / 删,owner-only。
|
|
62064
62236
|
"contact:list": ADMIN_ANY,
|
|
62065
62237
|
"contact:pin": ADMIN_ANY,
|
|
62238
|
+
"contact:setNote": ADMIN_ANY,
|
|
62066
62239
|
"contact:remove": ADMIN_ANY,
|
|
62067
62240
|
// contact:setRemoteAccess (owner UI 配远程访问授权):ADMIN_ANY
|
|
62068
62241
|
// (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
|
|
@@ -62312,7 +62485,7 @@ function computeMethodAccess(args) {
|
|
|
62312
62485
|
}
|
|
62313
62486
|
|
|
62314
62487
|
// src/version.ts
|
|
62315
|
-
var version = "0.2.
|
|
62488
|
+
var version = "0.2.425".length > 0 ? "0.2.425" : "dev";
|
|
62316
62489
|
|
|
62317
62490
|
// src/cli-probe/probe.ts
|
|
62318
62491
|
var fs65 = __toESM(require("fs"), 1);
|
|
@@ -65150,11 +65323,11 @@ init_protocol();
|
|
|
65150
65323
|
// src/peer-ops/run-command.ts
|
|
65151
65324
|
var import_node_child_process18 = require("child_process");
|
|
65152
65325
|
var import_node_os23 = __toESM(require("os"), 1);
|
|
65153
|
-
var
|
|
65326
|
+
var DEFAULT_TIMEOUT_MS5 = 3e4;
|
|
65154
65327
|
var MAX_TIMEOUT_MS = 3e5;
|
|
65155
65328
|
var MAX_OUTPUT_BYTES = 1048576;
|
|
65156
65329
|
function runCommand(command, opts = {}) {
|
|
65157
|
-
const timeoutMs = Math.min(opts.timeoutMs ??
|
|
65330
|
+
const timeoutMs = Math.min(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS5, MAX_TIMEOUT_MS);
|
|
65158
65331
|
return new Promise((resolve9) => {
|
|
65159
65332
|
const child = (0, import_node_child_process18.spawn)("bash", ["-c", command], { cwd: import_node_os23.default.homedir() });
|
|
65160
65333
|
const out = [];
|
|
@@ -65458,11 +65631,11 @@ var IosInterestClientError = class extends Error {
|
|
|
65458
65631
|
code;
|
|
65459
65632
|
cause;
|
|
65460
65633
|
};
|
|
65461
|
-
var
|
|
65634
|
+
var DEFAULT_TIMEOUT_MS6 = 8e3;
|
|
65462
65635
|
async function submitIosInterest(opts) {
|
|
65463
65636
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
65464
65637
|
const ac = new AbortController();
|
|
65465
|
-
const timer = setTimeout(() => ac.abort(), opts.timeoutMs ??
|
|
65638
|
+
const timer = setTimeout(() => ac.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS6);
|
|
65466
65639
|
let res;
|
|
65467
65640
|
try {
|
|
65468
65641
|
res = await doFetch(`${opts.apiUrl}/api/ios-interest`, {
|
|
@@ -66813,6 +66986,41 @@ async function startDaemon(config) {
|
|
|
66813
66986
|
const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
|
|
66814
66987
|
await publishedChannelStore.load();
|
|
66815
66988
|
const bundleCache = new BundleCache(bundleCacheRoot());
|
|
66989
|
+
const feishuDeviceDeps = {
|
|
66990
|
+
store: contactStore,
|
|
66991
|
+
exchange: async (deviceId) => {
|
|
66992
|
+
const record3 = ownerIdentityStore.read();
|
|
66993
|
+
if (!record3) {
|
|
66994
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:connect requires feishu login");
|
|
66995
|
+
}
|
|
66996
|
+
return centralExchange({
|
|
66997
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
66998
|
+
jwt: record3.ttcToken,
|
|
66999
|
+
provider: record3.identity.provider,
|
|
67000
|
+
deviceId,
|
|
67001
|
+
selfDeviceId: authFile.deviceId
|
|
67002
|
+
});
|
|
67003
|
+
},
|
|
67004
|
+
listDevices: async () => {
|
|
67005
|
+
const record3 = ownerIdentityStore.read();
|
|
67006
|
+
if (!record3) {
|
|
67007
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:list requires feishu login");
|
|
67008
|
+
}
|
|
67009
|
+
return centralListDevices({
|
|
67010
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
67011
|
+
ttcJwt: record3.ttcToken,
|
|
67012
|
+
provider: record3.identity.provider
|
|
67013
|
+
});
|
|
67014
|
+
},
|
|
67015
|
+
connectRemote,
|
|
67016
|
+
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
67017
|
+
// 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
|
|
67018
|
+
// 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
|
|
67019
|
+
// 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
|
|
67020
|
+
selfUrl: () => currentTunnelUrl,
|
|
67021
|
+
// device:connections 数据源。惰性闭包:wsServer 晚于本 deps 赋值,请求时才解引用。
|
|
67022
|
+
listOwnerConnections: () => wsServer?.listOwnerConnections() ?? []
|
|
67023
|
+
};
|
|
66816
67024
|
const loginFlow = new LoginFlow({
|
|
66817
67025
|
store: ownerIdentityStore,
|
|
66818
67026
|
fetchUserInfo: (ttcToken) => fetchTtcUserInfo({ apiBase: TTC_HOSTS.api, token: ttcToken }),
|
|
@@ -66841,7 +67049,23 @@ async function startDaemon(config) {
|
|
|
66841
67049
|
});
|
|
66842
67050
|
throw err;
|
|
66843
67051
|
}
|
|
66844
|
-
}
|
|
67052
|
+
},
|
|
67053
|
+
// org auto-mesh(spec 2026-08-31):登录成功后自动跟同组织的人建联。
|
|
67054
|
+
// 不进 fail-hard 链——login-flow 内部 void + catch,组网失败不拖垮登录。
|
|
67055
|
+
syncOrgMesh: () => syncOrgMesh({
|
|
67056
|
+
store: contactStore,
|
|
67057
|
+
fetchPeers: () => fetchOrgPeers({
|
|
67058
|
+
apiUrl: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
67059
|
+
apiKey: resolveClawdTicketsApiKey(),
|
|
67060
|
+
deviceId: authFile.deviceId,
|
|
67061
|
+
displayName: ownerIdentityStore.read()?.identity.displayName ?? authFile.deviceId
|
|
67062
|
+
}),
|
|
67063
|
+
connectToDevice: createConnectToDevice(feishuDeviceDeps),
|
|
67064
|
+
log: (msg, meta) => logger.info(msg, meta)
|
|
67065
|
+
}),
|
|
67066
|
+
// 组网整体失败(拉清单挂了)的出口:走 logger 才进 SLS——
|
|
67067
|
+
// "自动组网悄悄没发生"正是要靠日志排查的场景,console.warn 只有 stdout。
|
|
67068
|
+
logWarn: (msg, meta) => logger.warn(msg, meta)
|
|
66845
67069
|
});
|
|
66846
67070
|
const visitorStore = createVisitorStore({ dir: config.dataDir });
|
|
66847
67071
|
const exchangeVisitorToken = buildVisitorLogin({
|
|
@@ -67047,41 +67271,7 @@ async function startDaemon(config) {
|
|
|
67047
67271
|
// exchange/listDevices 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
|
|
67048
67272
|
// dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
|
|
67049
67273
|
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
67050
|
-
feishuDevice:
|
|
67051
|
-
store: contactStore,
|
|
67052
|
-
exchange: async (deviceId) => {
|
|
67053
|
-
const record3 = ownerIdentityStore.read();
|
|
67054
|
-
if (!record3) {
|
|
67055
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:connect requires feishu login");
|
|
67056
|
-
}
|
|
67057
|
-
return centralExchange({
|
|
67058
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
67059
|
-
jwt: record3.ttcToken,
|
|
67060
|
-
provider: record3.identity.provider,
|
|
67061
|
-
deviceId,
|
|
67062
|
-
selfDeviceId: authFile.deviceId
|
|
67063
|
-
});
|
|
67064
|
-
},
|
|
67065
|
-
listDevices: async () => {
|
|
67066
|
-
const record3 = ownerIdentityStore.read();
|
|
67067
|
-
if (!record3) {
|
|
67068
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:list requires feishu login");
|
|
67069
|
-
}
|
|
67070
|
-
return centralListDevices({
|
|
67071
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
67072
|
-
ttcJwt: record3.ttcToken,
|
|
67073
|
-
provider: record3.identity.provider
|
|
67074
|
-
});
|
|
67075
|
-
},
|
|
67076
|
-
connectRemote,
|
|
67077
|
-
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
67078
|
-
// 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
|
|
67079
|
-
// 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
|
|
67080
|
-
// 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
|
|
67081
|
-
selfUrl: () => currentTunnelUrl,
|
|
67082
|
-
// device:connections 数据源。惰性闭包:wsServer 晚于本 deps 赋值,请求时才解引用。
|
|
67083
|
-
listOwnerConnections: () => wsServer?.listOwnerConnections() ?? []
|
|
67084
|
-
},
|
|
67274
|
+
feishuDevice: feishuDeviceDeps,
|
|
67085
67275
|
// app-builder
|
|
67086
67276
|
appBuilderStore,
|
|
67087
67277
|
getProjectStore,
|