@clawos-dev/clawd 0.2.303 → 0.2.305

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +124 -78
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -52240,8 +52240,18 @@ function createLarkBotCloudClient(opts) {
52240
52240
  await request("POST", "/api/lark-bot/reply", { ...args });
52241
52241
  },
52242
52242
  async claimEvents() {
52243
- const json = await request("POST", "/api/lark-bot/events/claim");
52244
- return json.items ?? [];
52243
+ const json = await request("POST", "/api/lark-bot/events/claim?v=2");
52244
+ const items = json.items ?? [];
52245
+ return items.map((it) => {
52246
+ const rec3 = it;
52247
+ if (rec3 && typeof rec3 === "object" && "payload" in rec3) {
52248
+ return {
52249
+ status: rec3.status === "forwarded" ? "forwarded" : "received",
52250
+ payload: rec3.payload
52251
+ };
52252
+ }
52253
+ return { status: "received", payload: it };
52254
+ });
52245
52255
  },
52246
52256
  async fetchResource(appId, messageId, fileKey, maxBytes, type) {
52247
52257
  const ac = new AbortController();
@@ -52285,9 +52295,9 @@ var import_node_fs27 = __toESM(require("fs"), 1);
52285
52295
  var import_node_path28 = __toESM(require("path"), 1);
52286
52296
  init_protocol();
52287
52297
  var ERROR_REPLY_TEXT = "\u5904\u7406\u51FA\u9519\u4E86\uFF0C\u8BF7\u91CD\u8BD5";
52288
- var DEFAULT_TURN_TIMEOUT_MS = 10 * 60 * 1e3;
52298
+ var LOCAL_TURN_RETRIES = 2;
52299
+ var STOPPED_ERROR = "session stopped before reply";
52289
52300
  var DEBOUNCE_MS = 1e3;
52290
- var CONTENT_HOLD_MS = 6e4;
52291
52301
  var MAX_IMAGE_BYTES = 8 * 1024 * 1024;
52292
52302
  var EXT_BY_MIME = {
52293
52303
  "image/png": "png",
@@ -52312,70 +52322,83 @@ function safeAttachmentName(raw) {
52312
52322
  const ext = dot > 0 && cleaned.length - dot <= 20 ? cleaned.slice(dot) : "";
52313
52323
  return cleaned.slice(0, 100 - ext.length) + ext;
52314
52324
  }
52325
+ function routeClaimedEvents(items, opts) {
52326
+ const resume = [];
52327
+ for (const it of items) {
52328
+ opts.dedupe.add(it.payload.eventId);
52329
+ const env = { ...it.payload, iat: opts.nowSec, exp: opts.nowSec + 300 };
52330
+ if (it.status === "forwarded" && it.payload.type === "message") resume.push(env);
52331
+ else opts.router.handleEnvelope(env);
52332
+ }
52333
+ if (resume.length > 0) opts.router.handleResume(resume);
52334
+ }
52315
52335
  function createLarkChatRouter(deps) {
52316
- const timeoutMs = deps.turnTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS;
52317
52336
  const replyDelays = deps.replyRetryDelaysMs ?? [2e3, 8e3, 3e4];
52318
52337
  const sleep2 = deps.sleepImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
52319
52338
  const debounceMs = deps.debounceMs ?? DEBOUNCE_MS;
52320
- const contentHoldMs = deps.contentHoldMs ?? CONTENT_HOLD_MS;
52321
- const timerMsFor = (envs) => {
52322
- if (envs.some((e) => e.text?.trim())) return debounceMs;
52323
- const hasContent = envs.some(
52324
- (e) => (e.imageKeys?.length ?? 0) > 0 || (e.files?.length ?? 0) > 0
52325
- );
52326
- return hasContent ? contentHoldMs : debounceMs;
52327
- };
52328
52339
  const queues = /* @__PURE__ */ new Map();
52329
52340
  const activity = /* @__PURE__ */ new Map();
52330
52341
  const pending = /* @__PURE__ */ new Map();
52331
- const buildSection = async (env) => {
52332
- const imageLines = [];
52333
- for (const [i, key] of (env.imageKeys ?? []).entries()) {
52342
+ const imageLinesFor = async (env, ownerMessageId, keys) => {
52343
+ const lines = [];
52344
+ for (const [i, key] of keys.entries()) {
52334
52345
  try {
52335
52346
  if (!deps.fetchResource || !deps.mediaRootFor) throw new Error("resource pipeline not wired");
52336
- const r = await deps.fetchResource(env.appId, env.messageId, key, MAX_IMAGE_BYTES, "image");
52347
+ const r = await deps.fetchResource(env.appId, ownerMessageId, key, MAX_IMAGE_BYTES, "image");
52337
52348
  if (r.data.byteLength > MAX_IMAGE_BYTES) {
52338
52349
  deps.logger?.warn(`larkBot image oversize ${key}: ${r.data.byteLength}B`);
52339
- imageLines.push(IMAGE_OVERSIZE_TEXT);
52350
+ lines.push(IMAGE_OVERSIZE_TEXT);
52340
52351
  continue;
52341
52352
  }
52342
52353
  const ext = EXT_BY_MIME[r.contentType];
52343
52354
  if (!ext) {
52344
52355
  deps.logger?.warn(`larkBot image unsupported mime ${key}: ${r.contentType}`);
52345
- imageLines.push(IMAGE_UNAVAILABLE_TEXT);
52356
+ lines.push(IMAGE_UNAVAILABLE_TEXT);
52346
52357
  continue;
52347
52358
  }
52348
52359
  const dir = deps.mediaRootFor(env.chatId);
52349
52360
  import_node_fs27.default.mkdirSync(dir, { recursive: true });
52350
- const p2 = import_node_path28.default.join(dir, `${safeMediaName(env.messageId)}-${i}.${ext}`);
52361
+ const p2 = import_node_path28.default.join(dir, `${safeMediaName(ownerMessageId)}-${i}.${ext}`);
52351
52362
  import_node_fs27.default.writeFileSync(p2, r.data);
52352
- imageLines.push(`[\u56FE\u7247: ${p2}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
52363
+ lines.push(`[\u56FE\u7247: ${p2}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
52353
52364
  } catch (err) {
52354
52365
  deps.logger?.warn(`larkBot image fetch failed ${key}: ${err.message}`);
52355
- imageLines.push(IMAGE_UNAVAILABLE_TEXT);
52366
+ lines.push(IMAGE_UNAVAILABLE_TEXT);
52356
52367
  }
52357
52368
  }
52358
- const fileLines = [];
52359
- for (const [i, f] of (env.files ?? []).entries()) {
52369
+ return lines;
52370
+ };
52371
+ const fileLinesFor = async (env, ownerMessageId, entries) => {
52372
+ const lines = [];
52373
+ for (const [i, f] of entries.entries()) {
52360
52374
  try {
52361
52375
  if (!deps.fetchResource || !deps.mediaRootFor) throw new Error("resource pipeline not wired");
52362
- const r = await deps.fetchResource(env.appId, env.messageId, f.key, MAX_FILE_BYTES, "file");
52376
+ const r = await deps.fetchResource(env.appId, ownerMessageId, f.key, MAX_FILE_BYTES, "file");
52363
52377
  if (r.data.byteLength > MAX_FILE_BYTES) {
52364
52378
  deps.logger?.warn(`larkBot file oversize ${f.key}: ${r.data.byteLength}B`);
52365
- fileLines.push(FILE_OVERSIZE_TEXT(f.name));
52379
+ lines.push(FILE_OVERSIZE_TEXT(f.name));
52366
52380
  continue;
52367
52381
  }
52368
52382
  const dir = deps.mediaRootFor(env.chatId);
52369
52383
  import_node_fs27.default.mkdirSync(dir, { recursive: true });
52370
- const p2 = import_node_path28.default.join(dir, `${safeMediaName(env.messageId)}-${i}-${safeAttachmentName(f.name)}`);
52384
+ const p2 = import_node_path28.default.join(dir, `${safeMediaName(ownerMessageId)}-${i}-${safeAttachmentName(f.name)}`);
52371
52385
  import_node_fs27.default.writeFileSync(p2, r.data);
52372
- fileLines.push(`[\u6587\u4EF6: ${p2}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
52386
+ lines.push(`[\u6587\u4EF6: ${p2}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
52373
52387
  } catch (err) {
52374
52388
  deps.logger?.warn(`larkBot file fetch failed ${f.key}: ${err.message}`);
52375
- fileLines.push(FILE_UNAVAILABLE_TEXT(f.name));
52389
+ lines.push(FILE_UNAVAILABLE_TEXT(f.name));
52376
52390
  }
52377
52391
  }
52378
- return [env.text?.trim(), ...imageLines, ...fileLines].filter(Boolean).join("\n");
52392
+ return lines;
52393
+ };
52394
+ const buildSection = async (env) => {
52395
+ const parentLines = env.parentMessageId ? [
52396
+ ...await imageLinesFor(env, env.parentMessageId, env.parentImageKeys ?? []),
52397
+ ...await fileLinesFor(env, env.parentMessageId, env.parentFiles ?? [])
52398
+ ] : [];
52399
+ const imageLines = await imageLinesFor(env, env.messageId, env.imageKeys ?? []);
52400
+ const fileLines = await fileLinesFor(env, env.messageId, env.files ?? []);
52401
+ return [...parentLines, env.text?.trim(), ...imageLines, ...fileLines].filter(Boolean).join("\n");
52379
52402
  };
52380
52403
  const quoteAnnotation = (env, i, idxByMessageId) => {
52381
52404
  if (!env.parentMessageId) return void 0;
@@ -52384,7 +52407,15 @@ function createLarkChatRouter(deps) {
52384
52407
  if (j === i - 1) return void 0;
52385
52408
  return `[\u56DE\u590D\u7B2C${j + 1}\u6761]`;
52386
52409
  };
52387
- const runBatch = async (batchEnvs) => {
52410
+ const makeResumeText = (body, speaker) => `\u7EE7\u7EED
52411
+
52412
+ <system-reminder>\u300C\u7EE7\u7EED\u300D\u7531\u7CFB\u7EDF\u4EE3\u53D1\uFF1A\u6B64\u524D\u5904\u7406\u4EE5\u4E0B\u6D88\u606F\u65F6\u6267\u884C\u88AB\u6253\u65AD\u3002\u82E5\u4E0A\u6587\u5DF2\u6709\u5B83\u7684\u5904\u7406\u8FDB\u5EA6\uFF0C\u63A5\u7740\u5B8C\u6210\u672A\u5B8C\u6210\u7684\u90E8\u5206\uFF1B\u82E5\u4E0A\u6587\u6CA1\u6709\u76F8\u5173\u8BB0\u5F55\uFF0C\u628A\u4EE5\u4E0B\u5185\u5BB9\u5F53\u4F5C\u65B0\u6536\u5230\u7684\u6D88\u606F\u5904\u7406\u3002\u5904\u7406\u5B8C\u6309\u5E73\u5E38\u65B9\u5F0F\u56DE\u590D\uFF1A
52413
+ ---
52414
+ ${body}
52415
+ ---
52416
+ \uFF08\u6765\u81EA\u98DE\u4E66\u7FA4\u6210\u5458 ${speaker}\uFF09</system-reminder>`;
52417
+ const runBatch = async (batchEnvs, opts) => {
52418
+ if (deps.isShuttingDown?.()) return;
52388
52419
  const envs = batchEnvs.filter((e) => {
52389
52420
  if (e.messageId) return true;
52390
52421
  deps.logger?.warn(`larkBot chat-router: message envelope missing messageId, drop ${e.eventId}`);
@@ -52396,49 +52427,50 @@ function createLarkChatRouter(deps) {
52396
52427
  const idxByMessageId = new Map(envs.map((e, i) => [e.messageId, i]));
52397
52428
  const sections = [];
52398
52429
  for (const [i, env] of envs.entries()) {
52399
- const body2 = await buildSection(env);
52400
- const section = [quoteAnnotation(env, i, idxByMessageId), body2].filter(Boolean).join("\n");
52430
+ const sectionBody = await buildSection(env);
52431
+ const section = [quoteAnnotation(env, i, idxByMessageId), sectionBody].filter(Boolean).join("\n");
52401
52432
  if (section) sections.push(section);
52402
52433
  }
52403
52434
  const body = sections.join("\n") || "[\u975E\u6587\u672C\u6D88\u606F]";
52404
- const text = `${body}
52405
-
52406
- ${formatLarkSpeakerReminder(speaker)}`;
52407
- const turn = deps.manager.runLarkChatTurn({
52408
- personaId: last.personaId,
52409
- chatId: last.chatId,
52410
- chatName: last.chatName,
52411
- chatType: last.chatType ?? "group",
52412
- senderOpenId: last.senderOpenId ?? "",
52413
- senderName: last.senderName ?? last.senderOpenId ?? "\u672A\u77E5\u6210\u5458",
52414
- text
52415
- });
52416
- activity.set(last.chatId, {
52417
- chatId: last.chatId,
52418
- chatName: last.chatName,
52419
- lastActiveAt: Date.now(),
52420
- personaId: last.personaId
52421
- });
52422
- let timedOut = false;
52423
- let timer;
52424
- const timeout = new Promise((resolve6) => {
52425
- timer = setTimeout(() => {
52426
- timedOut = true;
52427
- turn.kill();
52428
- resolve6({ error: "turn timeout" });
52429
- }, timeoutMs);
52430
- });
52431
- let result;
52432
- try {
52433
- result = await Promise.race([turn.ended, timeout]);
52434
- } finally {
52435
- clearTimeout(timer);
52435
+ const runTurn = (text) => {
52436
+ const turn = deps.manager.runLarkChatTurn({
52437
+ personaId: last.personaId,
52438
+ chatId: last.chatId,
52439
+ chatName: last.chatName,
52440
+ chatType: last.chatType ?? "group",
52441
+ senderOpenId: last.senderOpenId ?? "",
52442
+ senderName: last.senderName ?? last.senderOpenId ?? "\u672A\u77E5\u6210\u5458",
52443
+ text
52444
+ });
52445
+ activity.set(last.chatId, {
52446
+ chatId: last.chatId,
52447
+ chatName: last.chatName,
52448
+ lastActiveAt: Date.now(),
52449
+ personaId: last.personaId
52450
+ });
52451
+ return turn.ended;
52452
+ };
52453
+ let result = await runTurn(
52454
+ opts?.resume ? makeResumeText(body, speaker) : `${body}
52455
+
52456
+ ${formatLarkSpeakerReminder(speaker)}`
52457
+ );
52458
+ for (let retry = 0; !("ok" in result); ) {
52459
+ if (deps.isShuttingDown?.()) {
52460
+ deps.logger?.info(`larkBot turn interrupted by shutdown (${last.chatId}), left for claim`);
52461
+ return;
52462
+ }
52463
+ if (result.error === STOPPED_ERROR) break;
52464
+ if (retry >= LOCAL_TURN_RETRIES) break;
52465
+ retry++;
52466
+ deps.logger?.warn(
52467
+ `larkBot turn error (${last.chatId}): ${result.error}; resume retry ${retry}/${LOCAL_TURN_RETRIES}`
52468
+ );
52469
+ result = await runTurn(makeResumeText(body, speaker));
52436
52470
  }
52437
52471
  const replyText = "ok" in result && result.ok ? result.replyText : ERROR_REPLY_TEXT;
52438
52472
  if (!("ok" in result)) {
52439
- deps.logger?.warn(
52440
- `larkBot turn ${timedOut ? "timeout" : "error"} (${last.chatId}): ${result.error}`
52441
- );
52473
+ deps.logger?.warn(`larkBot turn error (${last.chatId}): ${result.error}`);
52442
52474
  }
52443
52475
  const absorbed = envs.slice(0, -1).map((e) => e.eventId);
52444
52476
  for (let attempt = 0; attempt <= replyDelays.length; attempt++) {
@@ -52460,11 +52492,11 @@ ${formatLarkSpeakerReminder(speaker)}`;
52460
52492
  }
52461
52493
  }
52462
52494
  };
52463
- const enqueueBatch = (batchEnvs) => {
52495
+ const enqueueBatch = (batchEnvs, opts) => {
52464
52496
  const chatId = batchEnvs[0].chatId;
52465
52497
  const prev = queues.get(chatId) ?? Promise.resolve();
52466
52498
  const next = prev.then(
52467
- () => runBatch(batchEnvs).catch(
52499
+ () => runBatch(batchEnvs, opts).catch(
52468
52500
  (err) => deps.logger?.warn(`larkBot chat-router runBatch failed: ${err.message}`)
52469
52501
  )
52470
52502
  );
@@ -52501,15 +52533,26 @@ ${formatLarkSpeakerReminder(speaker)}`;
52501
52533
  if (batch) {
52502
52534
  batch.envelopes.push(env);
52503
52535
  clearTimeout(batch.timer);
52504
- batch.timer = setTimeout(() => flushPending(env.chatId), timerMsFor(batch.envelopes));
52536
+ batch.timer = setTimeout(() => flushPending(env.chatId), debounceMs);
52505
52537
  } else {
52506
52538
  pending.set(env.chatId, {
52507
52539
  envelopes: [env],
52508
52540
  senderOpenId: sender,
52509
- timer: setTimeout(() => flushPending(env.chatId), timerMsFor([env]))
52541
+ timer: setTimeout(() => flushPending(env.chatId), debounceMs)
52510
52542
  });
52511
52543
  }
52512
52544
  },
52545
+ handleResume(envelopes) {
52546
+ const groups = /* @__PURE__ */ new Map();
52547
+ for (const env of envelopes) {
52548
+ if (env.type !== "message") continue;
52549
+ const key = `${env.chatId}|${env.senderOpenId ?? ""}`;
52550
+ const group = groups.get(key);
52551
+ if (group) group.push(env);
52552
+ else groups.set(key, [env]);
52553
+ }
52554
+ for (const group of groups.values()) enqueueBatch(group, { resume: true });
52555
+ },
52513
52556
  listLarkSessions(personaId2) {
52514
52557
  return [...activity.values()].filter((a) => a.personaId === personaId2).map((a) => ({ chatId: a.chatId, chatName: a.chatName, lastActiveAt: a.lastActiveAt }));
52515
52558
  }
@@ -59269,7 +59312,7 @@ function computeMethodAccess(args) {
59269
59312
  }
59270
59313
 
59271
59314
  // src/version.ts
59272
- var version = "0.2.303".length > 0 ? "0.2.303" : "dev";
59315
+ var version = "0.2.305".length > 0 ? "0.2.305" : "dev";
59273
59316
 
59274
59317
  // src/cli-probe/probe.ts
59275
59318
  var fs56 = __toESM(require("fs"), 1);
@@ -63283,6 +63326,7 @@ async function startDaemon(config) {
63283
63326
  deviceId: authFile.deviceId,
63284
63327
  displayName: ownerDisplayName
63285
63328
  });
63329
+ let larkDraining = false;
63286
63330
  const larkChatRouter = createLarkChatRouter({
63287
63331
  manager,
63288
63332
  cloud: larkBotCloud,
@@ -63290,6 +63334,7 @@ async function startDaemon(config) {
63290
63334
  fetchResource: (appId, messageId, fileKey, maxBytes, type) => larkBotCloud.fetchResource(appId, messageId, fileKey, maxBytes, type),
63291
63335
  // 图片落 guest userWorkDir 下(capId = lark-<chat_id>):CC 经 --add-dir + allowRead carve 可 Read
63292
63336
  mediaRootFor: (chatId) => import_node_path65.default.join(deriveUserWorkDir(`lark-${chatId}`, usersRoot), "lark-media"),
63337
+ isShuttingDown: () => larkDraining,
63293
63338
  logger: {
63294
63339
  warn: (msg) => logger.warn(msg),
63295
63340
  info: (msg) => logger.info(msg)
@@ -63558,11 +63603,11 @@ async function startDaemon(config) {
63558
63603
  if (larkBotDeps.listBoundPersonaIds().length === 0) return;
63559
63604
  try {
63560
63605
  const items = await larkBotCloud.claimEvents();
63561
- const nowSec = Math.floor(Date.now() / 1e3);
63562
- for (const it of items) {
63563
- larkEventDedupe.add(it.eventId);
63564
- larkChatRouter.handleEnvelope({ ...it, iat: nowSec, exp: nowSec + 300 });
63565
- }
63606
+ routeClaimedEvents(items, {
63607
+ router: larkChatRouter,
63608
+ dedupe: larkEventDedupe,
63609
+ nowSec: Math.floor(Date.now() / 1e3)
63610
+ });
63566
63611
  if (items.length > 0) logger.info("larkBot pending events claimed", { count: items.length });
63567
63612
  } catch (err) {
63568
63613
  logger.warn("larkBot pending events claim failed", { err: err.message });
@@ -63638,6 +63683,7 @@ ${bar}
63638
63683
  attachmentGcInterval.unref();
63639
63684
  const shutdown = async () => {
63640
63685
  logger.info("stopping clawd");
63686
+ larkDraining = true;
63641
63687
  clearInterval(attachmentGcInterval);
63642
63688
  observer.stopAll();
63643
63689
  manager.stopAll();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.303",
3
+ "version": "0.2.305",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",