dsh-lark-bot 0.19.13 → 0.19.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README_EN.md +2 -2
- package/dist/cli.js +514 -47
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +6 -1
- package/dist/plugin.js +512 -45
- package/dist/plugin.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -6190,7 +6190,7 @@ async function runDoctor(options) {
|
|
|
6190
6190
|
|
|
6191
6191
|
// src/cli/commands/run.ts
|
|
6192
6192
|
import { mkdir as mkdir22 } from "fs/promises";
|
|
6193
|
-
import { randomUUID as
|
|
6193
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
6194
6194
|
import { join as join27 } from "path";
|
|
6195
6195
|
|
|
6196
6196
|
// src/bot/active-runs.ts
|
|
@@ -11061,7 +11061,7 @@ async function usage(ctx) {
|
|
|
11061
11061
|
|
|
11062
11062
|
// src/notify/sinks/channel-store.ts
|
|
11063
11063
|
import { readFile as readFile23 } from "fs/promises";
|
|
11064
|
-
var SINK_TYPES = ["telegram", "wecom"];
|
|
11064
|
+
var SINK_TYPES = ["telegram", "wecom", "wechat", "qq"];
|
|
11065
11065
|
var NotificationChannelStore = class {
|
|
11066
11066
|
constructor(path) {
|
|
11067
11067
|
this.path = path;
|
|
@@ -11142,7 +11142,8 @@ function normalizeChannel(id, value) {
|
|
|
11142
11142
|
if (!value || typeof value !== "object" || !id.trim()) return void 0;
|
|
11143
11143
|
const input = value;
|
|
11144
11144
|
const type = input.type;
|
|
11145
|
-
if (type !== "
|
|
11145
|
+
if (typeof type !== "string" || !SINK_TYPES.includes(type)) return void 0;
|
|
11146
|
+
const normalizedType = type;
|
|
11146
11147
|
const label2 = typeof input.label === "string" ? input.label.trim() : "";
|
|
11147
11148
|
const destination = typeof input.destination === "string" ? input.destination.trim() : "";
|
|
11148
11149
|
const secret = typeof input.secret === "string" ? input.secret.trim() : "";
|
|
@@ -11155,7 +11156,7 @@ function normalizeChannel(id, value) {
|
|
|
11155
11156
|
}
|
|
11156
11157
|
return {
|
|
11157
11158
|
id: id.trim(),
|
|
11158
|
-
type,
|
|
11159
|
+
type: normalizedType,
|
|
11159
11160
|
label: label2,
|
|
11160
11161
|
destination,
|
|
11161
11162
|
secret,
|
|
@@ -11177,7 +11178,307 @@ function maskSecret(value) {
|
|
|
11177
11178
|
return `${value.slice(0, 3)}\u2026${value.slice(-3)}`;
|
|
11178
11179
|
}
|
|
11179
11180
|
|
|
11181
|
+
// src/onboard/sink-qr.ts
|
|
11182
|
+
var SinkQrRegistry = class {
|
|
11183
|
+
constructor(providers = []) {
|
|
11184
|
+
this.providers = providers;
|
|
11185
|
+
}
|
|
11186
|
+
providers;
|
|
11187
|
+
forType(type) {
|
|
11188
|
+
return this.providers.find((provider) => provider.type === type);
|
|
11189
|
+
}
|
|
11190
|
+
has(type) {
|
|
11191
|
+
return this.forType(type) !== void 0;
|
|
11192
|
+
}
|
|
11193
|
+
supportedTypes() {
|
|
11194
|
+
return this.providers.map((provider) => provider.type);
|
|
11195
|
+
}
|
|
11196
|
+
};
|
|
11197
|
+
async function pollUntilCompleted(provider, sessionId, options = {}) {
|
|
11198
|
+
const interval = options.intervalMs ?? 3e3;
|
|
11199
|
+
const timeout = options.timeoutMs ?? 9e4;
|
|
11200
|
+
const signal = options.signal;
|
|
11201
|
+
const deadline = Date.now() + timeout;
|
|
11202
|
+
let last = { phase: "pending" };
|
|
11203
|
+
while (Date.now() < deadline) {
|
|
11204
|
+
if (signal?.aborted) return { phase: "failed", error: "aborted" };
|
|
11205
|
+
last = await provider.poll(sessionId, signal);
|
|
11206
|
+
if (last.phase !== "pending") return last;
|
|
11207
|
+
await sleep3(interval, signal);
|
|
11208
|
+
}
|
|
11209
|
+
return { phase: "expired" };
|
|
11210
|
+
}
|
|
11211
|
+
function sleep3(ms, signal) {
|
|
11212
|
+
return new Promise((resolve6, reject) => {
|
|
11213
|
+
if (signal?.aborted) return reject(abortError());
|
|
11214
|
+
const timer = setTimeout(resolve6, ms);
|
|
11215
|
+
timer.unref?.();
|
|
11216
|
+
signal?.addEventListener(
|
|
11217
|
+
"abort",
|
|
11218
|
+
() => {
|
|
11219
|
+
clearTimeout(timer);
|
|
11220
|
+
reject(abortError());
|
|
11221
|
+
},
|
|
11222
|
+
{ once: true }
|
|
11223
|
+
);
|
|
11224
|
+
});
|
|
11225
|
+
}
|
|
11226
|
+
function abortError() {
|
|
11227
|
+
const error = new Error("aborted");
|
|
11228
|
+
error.name = "AbortError";
|
|
11229
|
+
return error;
|
|
11230
|
+
}
|
|
11231
|
+
|
|
11232
|
+
// src/onboard/sink-qr-providers.ts
|
|
11233
|
+
import { createDecipheriv, randomBytes as randomBytes2, randomUUID as randomUUID8 } from "crypto";
|
|
11234
|
+
|
|
11235
|
+
// src/notify/sinks/text.ts
|
|
11236
|
+
function renderSinkText(message) {
|
|
11237
|
+
const zhLine = `${message.title.zh}\uFF08scope \`${message.scope}\`\uFF09${message.detail ? `\uFF1A${message.detail}` : ""}`;
|
|
11238
|
+
const enLine = `${message.title.en} (scope \`${message.scope}\`)${message.detail ? `: ${message.detail}` : ""}`;
|
|
11239
|
+
return `${stripMarkdown(zhLine)}
|
|
11240
|
+
|
|
11241
|
+
${stripMarkdown(enLine)}`;
|
|
11242
|
+
}
|
|
11243
|
+
function escapeHtml(value) {
|
|
11244
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11245
|
+
}
|
|
11246
|
+
function stripMarkdown(value) {
|
|
11247
|
+
return value.replaceAll("`", "").replaceAll("**", "").replaceAll("*", "");
|
|
11248
|
+
}
|
|
11249
|
+
|
|
11250
|
+
// src/notify/sinks/wechat.ts
|
|
11251
|
+
var WeChatIlinkSink = class {
|
|
11252
|
+
constructor(baseUrl = "https://ilinkai.weixin.qq.com", randomUin = () => String(Math.floor(Math.random() * 1e9))) {
|
|
11253
|
+
this.baseUrl = baseUrl;
|
|
11254
|
+
this.randomUin = randomUin;
|
|
11255
|
+
}
|
|
11256
|
+
baseUrl;
|
|
11257
|
+
randomUin;
|
|
11258
|
+
type = "wechat";
|
|
11259
|
+
async send(channel, message) {
|
|
11260
|
+
const token = channel.secret;
|
|
11261
|
+
const [toUserId, contextToken] = splitDestination(channel.destination);
|
|
11262
|
+
if (!token || !toUserId || !contextToken) {
|
|
11263
|
+
log.warn("sink:wechat", "missing-credential", { channel: channel.id });
|
|
11264
|
+
return false;
|
|
11265
|
+
}
|
|
11266
|
+
const url = `${this.baseUrl}/ilink/bot/sendmessage`;
|
|
11267
|
+
const body3 = JSON.stringify({
|
|
11268
|
+
msg: {
|
|
11269
|
+
from_user_id: "",
|
|
11270
|
+
to_user_id: toUserId,
|
|
11271
|
+
client_id: "",
|
|
11272
|
+
message_type: 2,
|
|
11273
|
+
message_state: 2,
|
|
11274
|
+
item_list: [{ type: 1, text_item: { text: renderSinkText(message) } }],
|
|
11275
|
+
context_token: contextToken
|
|
11276
|
+
},
|
|
11277
|
+
base_info: { channel_version: 1 }
|
|
11278
|
+
});
|
|
11279
|
+
const headers = {
|
|
11280
|
+
"content-type": "application/json",
|
|
11281
|
+
authorization: `Bearer ${token}`,
|
|
11282
|
+
AuthorizationType: "ilink_bot_token",
|
|
11283
|
+
"X-WECHAT-UIN": this.randomUin(),
|
|
11284
|
+
"iLink-App-Id": "bot",
|
|
11285
|
+
"iLink-App-ClientVersion": "131072"
|
|
11286
|
+
};
|
|
11287
|
+
try {
|
|
11288
|
+
const controller = new AbortController();
|
|
11289
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
11290
|
+
timer.unref?.();
|
|
11291
|
+
const response = await fetch(url, { method: "POST", headers, body: body3, signal: controller.signal });
|
|
11292
|
+
clearTimeout(timer);
|
|
11293
|
+
const payload = await response.json().catch(() => ({}));
|
|
11294
|
+
const ok = response.ok && (payload.ret === 0 || payload.ret === void 0);
|
|
11295
|
+
if (!ok) {
|
|
11296
|
+
log.warn("sink:wechat", "send-failed", {
|
|
11297
|
+
channel: channel.id,
|
|
11298
|
+
error: payload.msg ?? payload.errmsg ?? `http ${response.status}`
|
|
11299
|
+
});
|
|
11300
|
+
return false;
|
|
11301
|
+
}
|
|
11302
|
+
return true;
|
|
11303
|
+
} catch (error) {
|
|
11304
|
+
log.warn("sink:wechat", "send-error", { channel: channel.id, error });
|
|
11305
|
+
return false;
|
|
11306
|
+
}
|
|
11307
|
+
}
|
|
11308
|
+
};
|
|
11309
|
+
function splitDestination(value) {
|
|
11310
|
+
const sep3 = value.indexOf("|");
|
|
11311
|
+
if (sep3 === -1) return [value, ""];
|
|
11312
|
+
return [value.slice(0, sep3), value.slice(sep3 + 1)];
|
|
11313
|
+
}
|
|
11314
|
+
|
|
11315
|
+
// src/onboard/sink-qr-providers.ts
|
|
11316
|
+
var API_HEADERS = { "content-type": "application/json", accept: "application/json" };
|
|
11317
|
+
var BIND_STATUS = { NONE: 0, PENDING: 1, COMPLETED: 2, EXPIRED: 3 };
|
|
11318
|
+
var QqQrProvider = class {
|
|
11319
|
+
constructor(portalHost = process.env.DSH_LARK_QQ_PORTAL_HOST ?? "q.qq.com", fetchImpl = fetch, genKey = () => randomBytes2(32).toString("base64")) {
|
|
11320
|
+
this.portalHost = portalHost;
|
|
11321
|
+
this.fetchImpl = fetchImpl;
|
|
11322
|
+
this.genKey = genKey;
|
|
11323
|
+
}
|
|
11324
|
+
portalHost;
|
|
11325
|
+
fetchImpl;
|
|
11326
|
+
genKey;
|
|
11327
|
+
type = "qq";
|
|
11328
|
+
sessions = /* @__PURE__ */ new Map();
|
|
11329
|
+
async begin(_options) {
|
|
11330
|
+
const aesKey = this.genKey();
|
|
11331
|
+
const response = await this.fetchImpl(`https://${this.portalHost}/lite/create_bind_task`, {
|
|
11332
|
+
method: "POST",
|
|
11333
|
+
headers: API_HEADERS,
|
|
11334
|
+
body: JSON.stringify({ key: aesKey })
|
|
11335
|
+
});
|
|
11336
|
+
const payload = await response.json().catch(() => ({}));
|
|
11337
|
+
const taskId = payload.data?.task_id;
|
|
11338
|
+
if (!response.ok || !taskId || payload.retcode !== void 0 && payload.retcode !== 0) {
|
|
11339
|
+
throw new Error(`qq create_bind_task failed: ${payload.msg ?? ""} (http ${response.status})`);
|
|
11340
|
+
}
|
|
11341
|
+
const sessionId = randomUUID8();
|
|
11342
|
+
this.sessions.set(sessionId, { taskId, aesKey });
|
|
11343
|
+
const qrUrl = `https://q.qq.com/qqbot/openclaw/connect.html?task_id=${encodeURIComponent(taskId)}&_wv=2&source=dsh-lark-bot`;
|
|
11344
|
+
return { providerType: this.type, sessionId, qrUrl, expireIn: 600 };
|
|
11345
|
+
}
|
|
11346
|
+
async poll(sessionId, _signal) {
|
|
11347
|
+
const state = this.sessions.get(sessionId);
|
|
11348
|
+
if (!state) return { phase: "failed", error: "unknown session" };
|
|
11349
|
+
let data = {};
|
|
11350
|
+
try {
|
|
11351
|
+
const response = await this.fetchImpl(`https://${this.portalHost}/lite/poll_bind_result`, {
|
|
11352
|
+
method: "POST",
|
|
11353
|
+
headers: API_HEADERS,
|
|
11354
|
+
body: JSON.stringify({ task_id: state.taskId })
|
|
11355
|
+
});
|
|
11356
|
+
const payload = await response.json();
|
|
11357
|
+
if (!response.ok || payload.retcode !== void 0 && payload.retcode !== 0) return { phase: "pending" };
|
|
11358
|
+
data = payload.data ?? {};
|
|
11359
|
+
} catch {
|
|
11360
|
+
return { phase: "pending" };
|
|
11361
|
+
}
|
|
11362
|
+
const status = data.status ?? BIND_STATUS.NONE;
|
|
11363
|
+
if (status === BIND_STATUS.COMPLETED && data.bot_appid && data.bot_encrypt_secret) {
|
|
11364
|
+
let clientSecret;
|
|
11365
|
+
try {
|
|
11366
|
+
clientSecret = decryptSecret(data.bot_encrypt_secret, state.aesKey);
|
|
11367
|
+
} catch (error) {
|
|
11368
|
+
this.sessions.delete(sessionId);
|
|
11369
|
+
return { phase: "failed", error: `decrypt failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
11370
|
+
}
|
|
11371
|
+
const target = data.user_openid ? `user:${data.user_openid}` : "";
|
|
11372
|
+
this.sessions.delete(sessionId);
|
|
11373
|
+
return {
|
|
11374
|
+
phase: "completed",
|
|
11375
|
+
channel: {
|
|
11376
|
+
id: "",
|
|
11377
|
+
type: "qq",
|
|
11378
|
+
label: "QQ",
|
|
11379
|
+
destination: target,
|
|
11380
|
+
secret: `${data.bot_appid}:${clientSecret}`
|
|
11381
|
+
}
|
|
11382
|
+
};
|
|
11383
|
+
}
|
|
11384
|
+
if (status === BIND_STATUS.EXPIRED) {
|
|
11385
|
+
this.sessions.delete(sessionId);
|
|
11386
|
+
return { phase: "expired" };
|
|
11387
|
+
}
|
|
11388
|
+
return { phase: "pending" };
|
|
11389
|
+
}
|
|
11390
|
+
};
|
|
11391
|
+
var WeChatQrProvider = class {
|
|
11392
|
+
constructor(baseUrl = process.env.DSH_LARK_WECHAT_ILINK_URL ?? "https://ilinkai.weixin.qq.com", fetchImpl = fetch) {
|
|
11393
|
+
this.baseUrl = baseUrl;
|
|
11394
|
+
this.fetchImpl = fetchImpl;
|
|
11395
|
+
}
|
|
11396
|
+
baseUrl;
|
|
11397
|
+
fetchImpl;
|
|
11398
|
+
type = "wechat";
|
|
11399
|
+
sessions = /* @__PURE__ */ new Map();
|
|
11400
|
+
async begin(_options) {
|
|
11401
|
+
const response = await this.fetchImpl(`${this.baseUrl}/ilink/bot/get_bot_qrcode?bot_type=3`, {
|
|
11402
|
+
method: "GET"
|
|
11403
|
+
});
|
|
11404
|
+
const payload = await response.json().catch(() => ({}));
|
|
11405
|
+
const qrcode2 = payload.qrcode ?? payload.url ?? "";
|
|
11406
|
+
if (!response.ok || !qrcode2) {
|
|
11407
|
+
throw new Error(payload.msg ?? `wechat get_bot_qrcode failed: http ${response.status}`);
|
|
11408
|
+
}
|
|
11409
|
+
const sessionId = randomUUID8();
|
|
11410
|
+
this.sessions.set(sessionId, { qrcode: qrcode2 });
|
|
11411
|
+
return { providerType: this.type, sessionId, qrUrl: qrcode2, expireIn: 180 };
|
|
11412
|
+
}
|
|
11413
|
+
async poll(sessionId, _signal) {
|
|
11414
|
+
const state = this.sessions.get(sessionId);
|
|
11415
|
+
if (!state) return { phase: "failed", error: "unknown session" };
|
|
11416
|
+
const statusUrl = `${this.baseUrl}/ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(state.qrcode)}`;
|
|
11417
|
+
try {
|
|
11418
|
+
const response = await this.fetchImpl(statusUrl, {
|
|
11419
|
+
method: "GET",
|
|
11420
|
+
headers: { "iLink-App-Id": "bot", "iLink-App-ClientVersion": "131072" }
|
|
11421
|
+
});
|
|
11422
|
+
const payload = await response.json().catch(() => ({}));
|
|
11423
|
+
const status = String(payload.status ?? "wait").toLowerCase();
|
|
11424
|
+
if (status === "confirmed" || status === "success" || status === "scanned" || status === "2") {
|
|
11425
|
+
const token = payload.token ?? payload.access_token ?? "";
|
|
11426
|
+
const userId = payload.openid ?? payload.user_id ?? payload.to_user_id ?? "";
|
|
11427
|
+
this.sessions.delete(sessionId);
|
|
11428
|
+
return {
|
|
11429
|
+
phase: "completed",
|
|
11430
|
+
channel: { id: "", type: "wechat", label: "\u5FAE\u4FE1", destination: userId ? `${userId}|` : "", secret: token }
|
|
11431
|
+
};
|
|
11432
|
+
}
|
|
11433
|
+
if (status === "expired") {
|
|
11434
|
+
this.sessions.delete(sessionId);
|
|
11435
|
+
return { phase: "expired" };
|
|
11436
|
+
}
|
|
11437
|
+
return { phase: "pending" };
|
|
11438
|
+
} catch {
|
|
11439
|
+
return { phase: "pending" };
|
|
11440
|
+
}
|
|
11441
|
+
}
|
|
11442
|
+
};
|
|
11443
|
+
function buildSinkQrProviders() {
|
|
11444
|
+
return [new WeChatQrProvider(), new QqQrProvider()];
|
|
11445
|
+
}
|
|
11446
|
+
function coerceSinkQrChannel(input) {
|
|
11447
|
+
const destination = input.type === "wechat" ? normalizeWechatDestination(input.destination) : input.destination;
|
|
11448
|
+
return { id: input.id, type: input.type, label: input.label, destination, secret: input.secret };
|
|
11449
|
+
}
|
|
11450
|
+
function normalizeWechatDestination(value) {
|
|
11451
|
+
if (value.includes("|")) return value;
|
|
11452
|
+
const [id] = splitDestination(value);
|
|
11453
|
+
return id ? `${id}|` : value;
|
|
11454
|
+
}
|
|
11455
|
+
function decryptSecret(encryptedBase64, keyBase64) {
|
|
11456
|
+
const key = Buffer.from(keyBase64, "base64");
|
|
11457
|
+
const raw = Buffer.from(encryptedBase64, "base64");
|
|
11458
|
+
if (key.length !== 32) throw new Error("bind key must be 32 bytes");
|
|
11459
|
+
if (raw.length < 12 + 16) throw new Error("ciphertext too short");
|
|
11460
|
+
const iv = raw.subarray(0, 12);
|
|
11461
|
+
const body3 = raw.subarray(12);
|
|
11462
|
+
const tag = body3.subarray(body3.length - 16);
|
|
11463
|
+
const ciphertext = body3.subarray(0, body3.length - 16);
|
|
11464
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
11465
|
+
decipher.setAuthTag(tag);
|
|
11466
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
11467
|
+
}
|
|
11468
|
+
|
|
11469
|
+
// src/onboard/qr-image.ts
|
|
11470
|
+
import { toBuffer } from "qrcode";
|
|
11471
|
+
async function renderQrPng(value, options = {}) {
|
|
11472
|
+
return toBuffer(value, {
|
|
11473
|
+
type: "png",
|
|
11474
|
+
width: options.width ?? 400,
|
|
11475
|
+
margin: options.margin ?? 2,
|
|
11476
|
+
errorCorrectionLevel: "M"
|
|
11477
|
+
});
|
|
11478
|
+
}
|
|
11479
|
+
|
|
11180
11480
|
// src/commands/channels.ts
|
|
11481
|
+
var QR_REGISTRY = new SinkQrRegistry(buildSinkQrProviders());
|
|
11181
11482
|
async function reply6(ctx, zh, en) {
|
|
11182
11483
|
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
11183
11484
|
}
|
|
@@ -11206,6 +11507,8 @@ async function handleChannels(args, ctx) {
|
|
|
11206
11507
|
}
|
|
11207
11508
|
case "add":
|
|
11208
11509
|
return addChannel(ctx, store, rest.join(" "));
|
|
11510
|
+
case "accept":
|
|
11511
|
+
return acceptChannel(ctx, store, rest.join(" "));
|
|
11209
11512
|
case "remove": {
|
|
11210
11513
|
const id = rest[0];
|
|
11211
11514
|
if (!id) return usage2(ctx);
|
|
@@ -11250,6 +11553,7 @@ async function showChannel(ctx, channel) {
|
|
|
11250
11553
|
await reply6(ctx, zh, en);
|
|
11251
11554
|
}
|
|
11252
11555
|
async function addChannel(ctx, store, raw) {
|
|
11556
|
+
if (raw.trim().startsWith("--qr")) return addChannelByQr(ctx, store, raw.trim());
|
|
11253
11557
|
const parsed = parseAddArgs(raw);
|
|
11254
11558
|
if (!parsed || !parsed.type || !parsed.label || !parsed.destination || !parsed.secret) {
|
|
11255
11559
|
await usage2(ctx);
|
|
@@ -11275,14 +11579,116 @@ async function addChannel(ctx, store, raw) {
|
|
|
11275
11579
|
await store.add(channel);
|
|
11276
11580
|
await reply6(ctx, `\u5DF2\u6DFB\u52A0\u901A\u77E5\u6E20\u9053 ${maskChannel(channel)}\u3002`, `Added notification channel ${maskChannel(channel)}.`);
|
|
11277
11581
|
}
|
|
11582
|
+
function platformName(type) {
|
|
11583
|
+
const names = {
|
|
11584
|
+
telegram: "Telegram",
|
|
11585
|
+
wecom: "\u4F01\u4E1A\u5FAE\u4FE1",
|
|
11586
|
+
wechat: "\u5FAE\u4FE1",
|
|
11587
|
+
qq: "QQ"
|
|
11588
|
+
};
|
|
11589
|
+
return names[type] ?? type;
|
|
11590
|
+
}
|
|
11591
|
+
async function addChannelByQr(ctx, store, raw) {
|
|
11592
|
+
const parsed = parseQrArgs(raw);
|
|
11593
|
+
const type = parsed.type;
|
|
11594
|
+
if (!type) {
|
|
11595
|
+
await reply6(ctx, "\u7528\u6CD5\uFF1A`/channels add --qr <wechat|qq|telegram> [--id <id>] [--label <label>]`", "Usage: `/channels add --qr <wechat|qq|telegram> [--id <id>] [--label <label>]`");
|
|
11596
|
+
return;
|
|
11597
|
+
}
|
|
11598
|
+
if (!SINK_TYPES.includes(type)) {
|
|
11599
|
+
await reply6(ctx, `\u4E0D\u652F\u6301\u7684\u6E20\u9053\u7C7B\u578B\uFF1A\`${type}\`\u3002\u53EF\u7528\uFF1A\`${SINK_TYPES.join("|")}\`\u3002`, `Unsupported channel type: \`${type}\`. Available: \`${SINK_TYPES.join("|")}\`.`);
|
|
11600
|
+
return;
|
|
11601
|
+
}
|
|
11602
|
+
const provider = QR_REGISTRY.forType(type);
|
|
11603
|
+
if (!provider) {
|
|
11604
|
+
await reply6(ctx, `\`${type}\` \u6682\u65E0\u626B\u7801\u7ED1\u5B9A\u652F\u6301\uFF0C\u8BF7\u6539\u7528 \`/channels add ${type} <label> --destination <\u76EE\u6807> --secret <\u5BC6\u94A5>\`\u3002`, `\`${type}\` has no QR binding yet. Use \`/channels add ${type} <label> --destination <target> --secret <token>\` instead.`);
|
|
11605
|
+
return;
|
|
11606
|
+
}
|
|
11607
|
+
const id = parsed.id ?? `${type}-qr-${slugify2(parsed.label ?? platformName(type))}`;
|
|
11608
|
+
if (store.get(id)) {
|
|
11609
|
+
await reply6(ctx, `\u901A\u77E5\u6E20\u9053 \`${id}\` \u5DF2\u5B58\u5728\u3002\u7528 \`/channels remove ${id}\` \u5220\u9664\u540E\u91CD\u5EFA\u3002`, `Notification channel \`${id}\` already exists. Use \`/channels remove ${id}\` to recreate it.`);
|
|
11610
|
+
return;
|
|
11611
|
+
}
|
|
11612
|
+
let session;
|
|
11613
|
+
try {
|
|
11614
|
+
session = await provider.begin({ id, label: parsed.label ?? platformName(type) });
|
|
11615
|
+
} catch (error) {
|
|
11616
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11617
|
+
await reply6(
|
|
11618
|
+
ctx,
|
|
11619
|
+
`\u65E0\u6CD5\u53D1\u8D77 ${platformName(type)} \u626B\u7801\u7ED1\u5B9A\uFF1A${message}\u3002\u8BF7\u6539\u7528\u624B\u52A8\u65B9\u5F0F \`/channels add ${type} <label> --destination <\u76EE\u6807> --secret <\u5BC6\u94A5>\`\u3002`,
|
|
11620
|
+
`Could not start a ${platformName(type)} QR bind: ${message}. Use manual entry \`/channels add ${type} <label> --destination <target> --secret <token>\` instead.`
|
|
11621
|
+
);
|
|
11622
|
+
return;
|
|
11623
|
+
}
|
|
11624
|
+
const png = await renderQrPng(session.qrUrl);
|
|
11625
|
+
const minutes = Math.max(1, Math.round(session.expireIn / 60));
|
|
11626
|
+
const introZh = `${platformName(type)}\u626B\u7801\u7ED1\u5B9A\uFF1A\u8BF7\u7528\u300C${platformName(type)}\u300DApp \u626B\u63CF\u4E0B\u65B9\u4E8C\u7EF4\u7801\u521B\u5EFA / \u7ED1\u5B9A\u901A\u77E5\u4F1A\u8BDD\uFF0C\u4E8C\u7EF4\u7801\u7EA6 ${minutes} \u5206\u949F\u6709\u6548\u3002\u626B\u7801\u540E\u7B49\u5F85\u81EA\u52A8\u5B8C\u6210\uFF0C\u6216\u8D85\u65F6\u540E\u7528 \`/channels accept ${type} ${id} ${parsed.label ?? platformName(type)} <\u76EE\u6807> <\u5BC6\u94A5>\`\u3002`;
|
|
11627
|
+
const introEn = `${platformName(type)} QR bind: scan the code below with the ${platformName(type)} app to create / bind the notification session. It stays valid for about ${minutes} minute(s). After scanning it auto-completes, or use \`/channels accept ${type} ${id} ${parsed.label ?? platformName(type)} <target> <token>\` on timeout.`;
|
|
11628
|
+
if (typeof ctx.channel.sendImage === "function") {
|
|
11629
|
+
await ctx.channel.sendImage(ctx.chatId, png, { replyTo: ctx.messageId });
|
|
11630
|
+
} else {
|
|
11631
|
+
await reply6(ctx, `\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u4E0D\u652F\u6301\u53D1\u9001\u56FE\u7247\uFF0C\u8BF7\u6D4F\u89C8\u5668\u6253\u5F00\uFF1A${session.qrUrl}`, `Image sending is unavailable here; open in a browser: ${session.qrUrl}`);
|
|
11632
|
+
}
|
|
11633
|
+
await reply6(ctx, introZh, introEn);
|
|
11634
|
+
const result = await pollUntilCompleted(provider, session.sessionId, { timeoutMs: 9e4 });
|
|
11635
|
+
if (result.phase === "completed" && result.channel) {
|
|
11636
|
+
const channel = {
|
|
11637
|
+
...coerceSinkQrChannel({
|
|
11638
|
+
id,
|
|
11639
|
+
type,
|
|
11640
|
+
label: result.channel.label || parsed.label || platformName(type),
|
|
11641
|
+
destination: result.channel.destination,
|
|
11642
|
+
secret: result.channel.secret
|
|
11643
|
+
}),
|
|
11644
|
+
enabled: true
|
|
11645
|
+
};
|
|
11646
|
+
await store.add(channel);
|
|
11647
|
+
await reply6(ctx, `\u2705 \u5DF2\u7ED1\u5B9A\u5E76\u6DFB\u52A0\u901A\u77E5\u6E20\u9053 ${maskChannel(channel)}\u3002`, `\u2705 Bound and added notification channel ${maskChannel(channel)}.`);
|
|
11648
|
+
return;
|
|
11649
|
+
}
|
|
11650
|
+
const reason = result.phase === "expired" ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F" : result.error ?? "\u672A\u5728\u7B49\u5F85\u65F6\u95F4\u5185\u5B8C\u6210";
|
|
11651
|
+
await reply6(
|
|
11652
|
+
ctx,
|
|
11653
|
+
`\u672A\u5B8C\u6210${platformName(type)}\u626B\u7801\u7ED1\u5B9A\uFF08${reason}\uFF09\u3002\u8BF7\u91CD\u65B0\u7528 \`/channels add --qr ${type}\` \u53D1\u8D77\uFF0C\u6216\u624B\u52A8\u586B \`/channels add ${type} <label> --destination <\u76EE\u6807> --secret <\u5BC6\u94A5>\`\u3002`,
|
|
11654
|
+
`${platformName(type)} QR bind did not complete (${reason}). Retry \`/channels add --qr ${type}\`, or enter it manually with \`/channels add ${type} <label> --destination <target> --secret <token>\`.`
|
|
11655
|
+
);
|
|
11656
|
+
}
|
|
11657
|
+
async function acceptChannel(ctx, store, raw) {
|
|
11658
|
+
const [type, id, label2, destination, secret] = raw.trim().split(/\s+/);
|
|
11659
|
+
if (!type || !id || !label2 || !destination || !secret || !SINK_TYPES.includes(type)) {
|
|
11660
|
+
await reply6(ctx, "\u7528\u6CD5\uFF1A`/channels accept <type> <id> <label> <destination> <secret>`", "Usage: `/channels accept <type> <id> <label> <destination> <secret>`");
|
|
11661
|
+
return;
|
|
11662
|
+
}
|
|
11663
|
+
if (store.get(id)) {
|
|
11664
|
+
await reply6(ctx, `\u901A\u77E5\u6E20\u9053 \`${id}\` \u5DF2\u5B58\u5728\u3002\u7528 \`/channels remove ${id}\` \u5220\u9664\u540E\u91CD\u5EFA\u3002`, `Notification channel \`${id}\` already exists. Remove it first.`);
|
|
11665
|
+
return;
|
|
11666
|
+
}
|
|
11667
|
+
const channel = {
|
|
11668
|
+
...coerceSinkQrChannel({ id, type, label: label2, destination, secret }),
|
|
11669
|
+
enabled: true
|
|
11670
|
+
};
|
|
11671
|
+
await store.add(channel);
|
|
11672
|
+
await reply6(ctx, `\u5DF2\u6DFB\u52A0\u901A\u77E5\u6E20\u9053 ${maskChannel(channel)}\u3002`, `Added notification channel ${maskChannel(channel)}.`);
|
|
11673
|
+
}
|
|
11674
|
+
function parseQrArgs(raw) {
|
|
11675
|
+
const typeMatch = raw.match(/--qr\s+(\S+)/);
|
|
11676
|
+
const idMatch = raw.match(/--id\s+(\S+)/);
|
|
11677
|
+
const labelMatch = raw.match(/--label\s+(.+?)(?:\s--|\s*$)/);
|
|
11678
|
+
return {
|
|
11679
|
+
...typeMatch ? { type: typeMatch[1] } : {},
|
|
11680
|
+
...idMatch ? { id: idMatch[1] } : {},
|
|
11681
|
+
...labelMatch ? { label: labelMatch[1].trim() } : {}
|
|
11682
|
+
};
|
|
11683
|
+
}
|
|
11278
11684
|
async function notFound(ctx, id) {
|
|
11279
11685
|
await reply6(ctx, `\u672A\u627E\u5230\u901A\u77E5\u6E20\u9053 \`${id}\`\u3002\u7528 \`/channels list\` \u67E5\u770B\u3002`, `Unknown notification channel \`${id}\`. Use \`/channels list\`.`);
|
|
11280
11686
|
}
|
|
11281
11687
|
async function usage2(ctx) {
|
|
11282
11688
|
await reply6(
|
|
11283
11689
|
ctx,
|
|
11284
|
-
"\u7528\u6CD5\uFF1A`/channels list|show <id>|add <telegram|
|
|
11285
|
-
"Usage: `/channels list|show <id>|add <telegram|
|
|
11690
|
+
"\u7528\u6CD5\uFF1A`/channels list|show <id>|add --qr <wechat|qq|telegram> [--id <id>] [--label <label>]|add <type> <label> [--id <id>] --destination <\u76EE\u6807> --secret <\u5BC6\u94A5>|accept <type> <id> <label> <destination> <secret>|remove <id>|enable <id>|disable <id>`",
|
|
11691
|
+
"Usage: `/channels list|show <id>|add --qr <wechat|qq|telegram> [--id <id>] [--label <label>]|add <type> <label> [--id <id>] --destination <target> --secret <token>|accept <type> <id> <label> <destination> <secret>|remove <id>|enable <id>|disable <id>`"
|
|
11286
11692
|
);
|
|
11287
11693
|
}
|
|
11288
11694
|
function parseAddArgs(raw) {
|
|
@@ -12856,6 +13262,13 @@ function adaptLarkChannel(channel) {
|
|
|
12856
13262
|
{ file: { source: content, fileName } },
|
|
12857
13263
|
toLarkSendOptions(options)
|
|
12858
13264
|
);
|
|
13265
|
+
},
|
|
13266
|
+
async sendImage(chatId, content, options) {
|
|
13267
|
+
await channel.send(
|
|
13268
|
+
chatId,
|
|
13269
|
+
{ image: { source: content } },
|
|
13270
|
+
toLarkSendOptions(options)
|
|
13271
|
+
);
|
|
12859
13272
|
}
|
|
12860
13273
|
};
|
|
12861
13274
|
return {
|
|
@@ -13300,7 +13713,7 @@ function safe(value, maxBytes) {
|
|
|
13300
13713
|
}
|
|
13301
13714
|
|
|
13302
13715
|
// src/session/projection-protocol.ts
|
|
13303
|
-
import { randomUUID as
|
|
13716
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
13304
13717
|
var WebSessionProjectionSource = class {
|
|
13305
13718
|
constructor(transport) {
|
|
13306
13719
|
this.transport = transport;
|
|
@@ -13339,7 +13752,7 @@ var WebSessionProjectionSource = class {
|
|
|
13339
13752
|
});
|
|
13340
13753
|
return { events, hasMore: value.hasMore === true };
|
|
13341
13754
|
}
|
|
13342
|
-
async prompt(sessionId, text, rpcId =
|
|
13755
|
+
async prompt(sessionId, text, rpcId = randomUUID9()) {
|
|
13343
13756
|
responseValue(await this.transport.rpc("session.prompt", {
|
|
13344
13757
|
sessionId,
|
|
13345
13758
|
mode: "queue",
|
|
@@ -14012,7 +14425,7 @@ function wait(ms) {
|
|
|
14012
14425
|
}
|
|
14013
14426
|
|
|
14014
14427
|
// src/commands/session-projection.ts
|
|
14015
|
-
import { randomUUID as
|
|
14428
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
14016
14429
|
var SessionProjectionController = class {
|
|
14017
14430
|
constructor(deps) {
|
|
14018
14431
|
this.deps = deps;
|
|
@@ -14152,7 +14565,7 @@ workspace: \`${workspaceCwd}\``
|
|
|
14152
14565
|
if (owner && owner.scope !== input.identity.scope && !this.deps.access.isAdmin(input.identity.actorId)) {
|
|
14153
14566
|
throw new Error("\u8BE5 session \u5DF2\u7ED1\u5B9A\u5176\u4ED6\u98DE\u4E66 scope\uFF1B\u72EC\u5360\u8FC1\u79FB\u4EC5 profile \u7BA1\u7406\u5458\u53EF\u786E\u8BA4");
|
|
14154
14567
|
}
|
|
14155
|
-
const nonce =
|
|
14568
|
+
const nonce = randomUUID10();
|
|
14156
14569
|
const expiresAt = Date.now() + (this.deps.confirmationTtlMs ?? 10 * 6e4);
|
|
14157
14570
|
const timer = setTimeout(() => this.pending.delete(nonce), expiresAt - Date.now());
|
|
14158
14571
|
timer.unref?.();
|
|
@@ -15177,13 +15590,19 @@ function attachRunCardAnchors(channel, anchors) {
|
|
|
15177
15590
|
const result = await channel.sendFile(chatId, fileName, content, options);
|
|
15178
15591
|
notify(chatId);
|
|
15179
15592
|
return result;
|
|
15593
|
+
},
|
|
15594
|
+
async sendImage(chatId, content, options) {
|
|
15595
|
+
if (typeof channel.sendImage !== "function") return void 0;
|
|
15596
|
+
const result = await channel.sendImage(chatId, content, options);
|
|
15597
|
+
notify(chatId);
|
|
15598
|
+
return result;
|
|
15180
15599
|
}
|
|
15181
15600
|
};
|
|
15182
15601
|
}
|
|
15183
15602
|
|
|
15184
15603
|
// src/notify/server.ts
|
|
15185
15604
|
import { createServer } from "http";
|
|
15186
|
-
import { randomBytes as
|
|
15605
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
15187
15606
|
var NotifyServer = class {
|
|
15188
15607
|
server;
|
|
15189
15608
|
token;
|
|
@@ -15470,7 +15889,7 @@ function readBody(req) {
|
|
|
15470
15889
|
});
|
|
15471
15890
|
}
|
|
15472
15891
|
function generateNotifyToken() {
|
|
15473
|
-
return `dsh-lark-${
|
|
15892
|
+
return `dsh-lark-${randomBytes3(18).toString("hex")}`;
|
|
15474
15893
|
}
|
|
15475
15894
|
|
|
15476
15895
|
// src/notify/ask-handler.ts
|
|
@@ -15662,7 +16081,7 @@ async function settleCancelledCard(deps, chatId, cardMessageId, options) {
|
|
|
15662
16081
|
}
|
|
15663
16082
|
|
|
15664
16083
|
// src/notify/approval-handler.ts
|
|
15665
|
-
import { randomUUID as
|
|
16084
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
15666
16085
|
init_tool_policy();
|
|
15667
16086
|
function buildApprovalHandler(deps) {
|
|
15668
16087
|
return async (payload, signal) => {
|
|
@@ -15705,7 +16124,7 @@ ${policyDenialText(denial)}
|
|
|
15705
16124
|
return { ok: true, outcome: "rejected", denial };
|
|
15706
16125
|
}
|
|
15707
16126
|
if (payload.lowRisk) return { ok: true, outcome: "allowed-once" };
|
|
15708
|
-
const id = `approval-${
|
|
16127
|
+
const id = `approval-${randomUUID11().replaceAll("-", "")}`;
|
|
15709
16128
|
const request = {
|
|
15710
16129
|
id,
|
|
15711
16130
|
...payload.callId === void 0 ? {} : { callId: payload.callId },
|
|
@@ -15949,21 +16368,6 @@ async function postJson(url, body3) {
|
|
|
15949
16368
|
}
|
|
15950
16369
|
}
|
|
15951
16370
|
|
|
15952
|
-
// src/notify/sinks/text.ts
|
|
15953
|
-
function renderSinkText(message) {
|
|
15954
|
-
const zhLine = `${message.title.zh}\uFF08scope \`${message.scope}\`\uFF09${message.detail ? `\uFF1A${message.detail}` : ""}`;
|
|
15955
|
-
const enLine = `${message.title.en} (scope \`${message.scope}\`)${message.detail ? `: ${message.detail}` : ""}`;
|
|
15956
|
-
return `${stripMarkdown(zhLine)}
|
|
15957
|
-
|
|
15958
|
-
${stripMarkdown(enLine)}`;
|
|
15959
|
-
}
|
|
15960
|
-
function escapeHtml(value) {
|
|
15961
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
15962
|
-
}
|
|
15963
|
-
function stripMarkdown(value) {
|
|
15964
|
-
return value.replaceAll("`", "").replaceAll("**", "").replaceAll("*", "");
|
|
15965
|
-
}
|
|
15966
|
-
|
|
15967
16371
|
// src/notify/sinks/telegram.ts
|
|
15968
16372
|
var TelegramSink = class {
|
|
15969
16373
|
constructor(baseUrl = "https://api.telegram.org") {
|
|
@@ -16038,9 +16442,72 @@ var WeComSink = class {
|
|
|
16038
16442
|
}
|
|
16039
16443
|
};
|
|
16040
16444
|
|
|
16445
|
+
// src/notify/sinks/qq.ts
|
|
16446
|
+
var QqSink = class {
|
|
16447
|
+
constructor(apiBase = "https://api.sgroup.qq.com", fetchAccessToken = getAppAccessToken) {
|
|
16448
|
+
this.apiBase = apiBase;
|
|
16449
|
+
this.fetchAccessToken = fetchAccessToken;
|
|
16450
|
+
}
|
|
16451
|
+
apiBase;
|
|
16452
|
+
fetchAccessToken;
|
|
16453
|
+
type = "qq";
|
|
16454
|
+
async send(channel, message) {
|
|
16455
|
+
const [appId, clientSecret] = splitAppCredential(channel.secret);
|
|
16456
|
+
const target = channel.destination;
|
|
16457
|
+
if (!appId || !clientSecret || !target) {
|
|
16458
|
+
log.warn("sink:qq", "missing-credential", { channel: channel.id });
|
|
16459
|
+
return false;
|
|
16460
|
+
}
|
|
16461
|
+
const isC2c = target.startsWith("user:");
|
|
16462
|
+
const openId = isC2c ? target.slice("user:".length) : target;
|
|
16463
|
+
const url = `${this.apiBase}/v2/${isC2c ? "users" : "groups"}/${encodeURIComponent(openId)}/messages`;
|
|
16464
|
+
try {
|
|
16465
|
+
const token = await this.fetchAccessToken(appId, clientSecret);
|
|
16466
|
+
const controller = new AbortController();
|
|
16467
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
16468
|
+
timer.unref?.();
|
|
16469
|
+
const response = await fetch(url, {
|
|
16470
|
+
method: "POST",
|
|
16471
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
16472
|
+
body: JSON.stringify({ content: renderSinkText(message), msg_type: 0 }),
|
|
16473
|
+
signal: controller.signal
|
|
16474
|
+
});
|
|
16475
|
+
clearTimeout(timer);
|
|
16476
|
+
const payload = await response.json().catch(() => ({}));
|
|
16477
|
+
if (!response.ok || payload.code !== void 0 && payload.code !== 0) {
|
|
16478
|
+
log.warn("sink:qq", "send-failed", {
|
|
16479
|
+
channel: channel.id,
|
|
16480
|
+
error: payload.message ?? `http ${response.status}`
|
|
16481
|
+
});
|
|
16482
|
+
return false;
|
|
16483
|
+
}
|
|
16484
|
+
return true;
|
|
16485
|
+
} catch (error) {
|
|
16486
|
+
log.warn("sink:qq", "send-error", { channel: channel.id, error });
|
|
16487
|
+
return false;
|
|
16488
|
+
}
|
|
16489
|
+
}
|
|
16490
|
+
};
|
|
16491
|
+
function splitAppCredential(value) {
|
|
16492
|
+
const sep3 = value.indexOf(":");
|
|
16493
|
+
if (sep3 === -1) return [value, ""];
|
|
16494
|
+
return [value.slice(0, sep3), value.slice(sep3 + 1)];
|
|
16495
|
+
}
|
|
16496
|
+
async function getAppAccessToken(appId, clientSecret) {
|
|
16497
|
+
const response = await postJson(
|
|
16498
|
+
"https://bots.qq.com/app/getAppAccessToken",
|
|
16499
|
+
JSON.stringify({ appId, clientSecret })
|
|
16500
|
+
);
|
|
16501
|
+
const payload = await response.json();
|
|
16502
|
+
if (!response.ok || !payload.access_token) {
|
|
16503
|
+
throw new Error(payload.message ?? `access token http ${response.status}`);
|
|
16504
|
+
}
|
|
16505
|
+
return payload.access_token;
|
|
16506
|
+
}
|
|
16507
|
+
|
|
16041
16508
|
// src/notify/sinks/registry.ts
|
|
16042
16509
|
var OutboundSinkRegistry = class {
|
|
16043
|
-
constructor(store, sinks = [new TelegramSink(), new WeComSink()]) {
|
|
16510
|
+
constructor(store, sinks = [new TelegramSink(), new WeComSink(), new WeChatIlinkSink(), new QqSink()]) {
|
|
16044
16511
|
this.store = store;
|
|
16045
16512
|
this.sinks = sinks;
|
|
16046
16513
|
}
|
|
@@ -16665,7 +17132,7 @@ function normalizeContextSnapshot(value) {
|
|
|
16665
17132
|
|
|
16666
17133
|
// src/session/projection-store.ts
|
|
16667
17134
|
import { readFile as readFile26 } from "fs/promises";
|
|
16668
|
-
import { randomUUID as
|
|
17135
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
16669
17136
|
var MAX_RECENT_MESSAGES = 64;
|
|
16670
17137
|
var MAX_PROMPT_CORRELATIONS = 64;
|
|
16671
17138
|
var PROMPT_CORRELATION_TTL_MS = 24 * 60 * 6e4;
|
|
@@ -16732,7 +17199,7 @@ var SessionProjectionStore = class {
|
|
|
16732
17199
|
recentMessages: currentOwner?.scope === input.scope && currentOwner.workspaceCwd === input.workspaceCwd ? structuredClone(currentOwner.recentMessages) : [],
|
|
16733
17200
|
promptCorrelations: structuredClone(currentOwner?.promptCorrelations ?? []),
|
|
16734
17201
|
boundAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16735
|
-
generationId:
|
|
17202
|
+
generationId: randomUUID12()
|
|
16736
17203
|
};
|
|
16737
17204
|
this.data.bindings[targetKey] = binding;
|
|
16738
17205
|
return {
|
|
@@ -16889,7 +17356,7 @@ function normalizeCorrelation(value) {
|
|
|
16889
17356
|
|
|
16890
17357
|
// src/session/archive.ts
|
|
16891
17358
|
import { execFile as execFile2 } from "child_process";
|
|
16892
|
-
import { randomBytes as
|
|
17359
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
16893
17360
|
import { access, mkdir as mkdir17, readdir as readdir3, readFile as readFile27, unlink, writeFile as writeFile8 } from "fs/promises";
|
|
16894
17361
|
import { join as join24 } from "path";
|
|
16895
17362
|
import { promisify } from "util";
|
|
@@ -16900,7 +17367,7 @@ function archiveScopeSlug(scope) {
|
|
|
16900
17367
|
}
|
|
16901
17368
|
function timestampId() {
|
|
16902
17369
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
|
|
16903
|
-
return `${stamp}-${
|
|
17370
|
+
return `${stamp}-${randomBytes4(3).toString("hex")}`;
|
|
16904
17371
|
}
|
|
16905
17372
|
function renderMarkdown(input, record) {
|
|
16906
17373
|
const lines = [
|
|
@@ -17214,7 +17681,7 @@ async function safeRemove(path) {
|
|
|
17214
17681
|
|
|
17215
17682
|
// src/workspace/git-worktree.ts
|
|
17216
17683
|
import { execFile as execFile3 } from "child_process";
|
|
17217
|
-
import { createHash as createHash2, randomBytes as
|
|
17684
|
+
import { createHash as createHash2, randomBytes as randomBytes5 } from "crypto";
|
|
17218
17685
|
import { access as access2, copyFile, mkdir as mkdir18, realpath as realpath2 } from "fs/promises";
|
|
17219
17686
|
import { dirname as dirname16, join as join25 } from "path";
|
|
17220
17687
|
import { promisify as promisify2 } from "util";
|
|
@@ -17275,7 +17742,7 @@ var GitWorktreeManager = class {
|
|
|
17275
17742
|
return { cwd: target, created: false };
|
|
17276
17743
|
}
|
|
17277
17744
|
}
|
|
17278
|
-
const branch = `dsh-lark/${slug}-${Date.now().toString(36)}-${
|
|
17745
|
+
const branch = `dsh-lark/${slug}-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`;
|
|
17279
17746
|
await mkdir18(this.options.worktreesRoot, { recursive: true });
|
|
17280
17747
|
await this.runGit(["worktree", "add", "-b", branch, target, "HEAD"], base);
|
|
17281
17748
|
await this.ensureProjectRules(base, target);
|
|
@@ -18259,7 +18726,7 @@ function projectDiagnosticLogs(lines) {
|
|
|
18259
18726
|
}
|
|
18260
18727
|
|
|
18261
18728
|
// src/secret/registry.ts
|
|
18262
|
-
import { randomUUID as
|
|
18729
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
18263
18730
|
var SecretRequestRegistry = class {
|
|
18264
18731
|
constructor(writer, options = {}) {
|
|
18265
18732
|
this.writer = writer;
|
|
@@ -18272,7 +18739,7 @@ var SecretRequestRegistry = class {
|
|
|
18272
18739
|
now;
|
|
18273
18740
|
register(input) {
|
|
18274
18741
|
this.writer.validate(input.target, input.reference);
|
|
18275
|
-
const id = `secret-${
|
|
18742
|
+
const id = `secret-${randomUUID13().replaceAll("-", "")}`;
|
|
18276
18743
|
let resolve6;
|
|
18277
18744
|
const promise = new Promise((settle) => {
|
|
18278
18745
|
resolve6 = settle;
|
|
@@ -18422,7 +18889,7 @@ function buildSecretHandler(deps) {
|
|
|
18422
18889
|
init_own_package();
|
|
18423
18890
|
|
|
18424
18891
|
// src/guardian/update-handoff.ts
|
|
18425
|
-
import { createHash as createHash3, randomUUID as
|
|
18892
|
+
import { createHash as createHash3, randomUUID as randomUUID14 } from "crypto";
|
|
18426
18893
|
import { spawn as spawn7 } from "child_process";
|
|
18427
18894
|
import { chmod, mkdir as mkdir21, readFile as readFile32, rm as rm10 } from "fs/promises";
|
|
18428
18895
|
import { dirname as dirname19, join as join26 } from "path";
|
|
@@ -18596,7 +19063,7 @@ var GuardianUpdateHandoff = class {
|
|
|
18596
19063
|
this.options = options;
|
|
18597
19064
|
this.launch = options.launch ?? defaultLaunch;
|
|
18598
19065
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
18599
|
-
this.id = options.id ??
|
|
19066
|
+
this.id = options.id ?? randomUUID14;
|
|
18600
19067
|
}
|
|
18601
19068
|
options;
|
|
18602
19069
|
launch;
|
|
@@ -18712,14 +19179,14 @@ var GuardianUpdateHandoff = class {
|
|
|
18712
19179
|
};
|
|
18713
19180
|
|
|
18714
19181
|
// src/upgrade/channel-update.ts
|
|
18715
|
-
import { randomUUID as
|
|
19182
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
18716
19183
|
var OFFER_TTL_MS = 10 * 6e4;
|
|
18717
19184
|
var ChannelUpdateController = class {
|
|
18718
19185
|
constructor(options) {
|
|
18719
19186
|
this.options = options;
|
|
18720
19187
|
this.current = options.current ?? currentVersion();
|
|
18721
19188
|
this.probe = options.probe ?? (() => latestVersion({ cacheMs: 0 }));
|
|
18722
|
-
this.id = options.id ??
|
|
19189
|
+
this.id = options.id ?? randomUUID15;
|
|
18723
19190
|
this.now = options.now ?? Date.now;
|
|
18724
19191
|
this.ttl = options.offerTtlMs ?? OFFER_TTL_MS;
|
|
18725
19192
|
}
|
|
@@ -19078,7 +19545,7 @@ async function startBridgeEngine(options) {
|
|
|
19078
19545
|
ledgerClaimed = await claimJobDispatch({
|
|
19079
19546
|
jobs,
|
|
19080
19547
|
messageIds: ledgerMessageIds,
|
|
19081
|
-
runId: `dispatch-${
|
|
19548
|
+
runId: `dispatch-${randomUUID16()}`,
|
|
19082
19549
|
first,
|
|
19083
19550
|
channel: streaming
|
|
19084
19551
|
});
|
|
@@ -20193,7 +20660,7 @@ ${changeLines.join("\n")}
|
|
|
20193
20660
|
import { join as join33 } from "path";
|
|
20194
20661
|
|
|
20195
20662
|
// src/guardian/service.ts
|
|
20196
|
-
import { randomUUID as
|
|
20663
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
20197
20664
|
import { readFileSync as readFileSync6 } from "fs";
|
|
20198
20665
|
import { homedir as homedir21 } from "os";
|
|
20199
20666
|
import { join as join32 } from "path";
|
|
@@ -21040,7 +21507,7 @@ var GuardianService = class {
|
|
|
21040
21507
|
}
|
|
21041
21508
|
const transcript = this.transcripts.get(scope) ?? [];
|
|
21042
21509
|
const prompt = buildSafePrompt(transcript, msg.content);
|
|
21043
|
-
const runId =
|
|
21510
|
+
const runId = randomUUID17();
|
|
21044
21511
|
const density = this.options.safeDensity;
|
|
21045
21512
|
const timeoutMs = this.options.safeTimeoutMs;
|
|
21046
21513
|
const now = Date.now();
|
|
@@ -21741,7 +22208,7 @@ function waitForExit(child) {
|
|
|
21741
22208
|
child.once("error", done);
|
|
21742
22209
|
});
|
|
21743
22210
|
}
|
|
21744
|
-
function
|
|
22211
|
+
function sleep4(ms) {
|
|
21745
22212
|
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
21746
22213
|
}
|
|
21747
22214
|
async function runSupervise(options, deps = {}) {
|
|
@@ -21811,7 +22278,7 @@ async function runSupervise(options, deps = {}) {
|
|
|
21811
22278
|
restarts += 1;
|
|
21812
22279
|
await writeStatus("restarting", void 0, restarts);
|
|
21813
22280
|
const backoff = computeBackoffMs(restarts);
|
|
21814
|
-
await
|
|
22281
|
+
await sleep4(backoff);
|
|
21815
22282
|
}
|
|
21816
22283
|
} finally {
|
|
21817
22284
|
if (forceStopTimer) clearTimeout(forceStopTimer);
|