@clawos-dev/clawd 0.2.298 → 0.2.300

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 +137 -42
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -52021,14 +52021,15 @@ function createLarkBotCloudClient(opts) {
52021
52021
  const json = await request("POST", "/api/lark-bot/events/claim");
52022
52022
  return json.items ?? [];
52023
52023
  },
52024
- async fetchResource(appId, messageId, fileKey, maxBytes) {
52024
+ async fetchResource(appId, messageId, fileKey, maxBytes, type) {
52025
52025
  const ac = new AbortController();
52026
52026
  const timer = setTimeout(() => ac.abort(), opts.timeoutMs ?? 15e3);
52027
52027
  const qs = new URLSearchParams({
52028
52028
  appId,
52029
52029
  messageId,
52030
52030
  fileKey,
52031
- ...maxBytes ? { maxBytes: String(maxBytes) } : {}
52031
+ ...maxBytes ? { maxBytes: String(maxBytes) } : {},
52032
+ ...type ? { type } : {}
52032
52033
  }).toString();
52033
52034
  let res;
52034
52035
  try {
@@ -52063,6 +52064,8 @@ var import_node_path28 = __toESM(require("path"), 1);
52063
52064
  init_protocol();
52064
52065
  var ERROR_REPLY_TEXT = "\u5904\u7406\u51FA\u9519\u4E86\uFF0C\u8BF7\u91CD\u8BD5";
52065
52066
  var DEFAULT_TURN_TIMEOUT_MS = 10 * 60 * 1e3;
52067
+ var DEBOUNCE_MS = 1e3;
52068
+ var CONTENT_HOLD_MS = 6e4;
52066
52069
  var MAX_IMAGE_BYTES = 8 * 1024 * 1024;
52067
52070
  var EXT_BY_MIME = {
52068
52071
  "image/png": "png",
@@ -52072,27 +52075,43 @@ var EXT_BY_MIME = {
52072
52075
  };
52073
52076
  var IMAGE_OVERSIZE_TEXT = "[\u56FE\u7247\u6D88\u606F\uFF0C\u8D85\u51FA\u5927\u5C0F\u9650\u5236]";
52074
52077
  var IMAGE_UNAVAILABLE_TEXT = "[\u56FE\u7247\u6D88\u606F\uFF0C\u6682\u4E0D\u652F\u6301\u67E5\u770B]";
52078
+ var MAX_FILE_BYTES = 20 * 1024 * 1024;
52079
+ var FILE_OVERSIZE_TEXT = (name) => `[\u6587\u4EF6: ${name}\uFF0C\u8D85\u51FA\u5927\u5C0F\u9650\u5236]`;
52080
+ var FILE_UNAVAILABLE_TEXT = (name) => `[\u6587\u4EF6: ${name}\uFF0C\u62C9\u53D6\u5931\u8D25]`;
52075
52081
  function safeMediaName(raw) {
52076
52082
  const cleaned = raw.replace(/[^a-zA-Z0-9_\-.]/g, "_").replace(/^\.+/, (d) => "_".repeat(d.length));
52077
52083
  return (cleaned || "msg").replace(/\.\./g, "__");
52078
52084
  }
52085
+ function safeAttachmentName(raw) {
52086
+ const cleaned = raw.replace(/[/\\\u0000-\u001f]/g, "_").replace(/\.\./g, "__").replace(/^\.+/, (d) => "_".repeat(d.length));
52087
+ if (!cleaned) return "file";
52088
+ if (cleaned.length <= 100) return cleaned;
52089
+ const dot = cleaned.lastIndexOf(".");
52090
+ const ext = dot > 0 && cleaned.length - dot <= 20 ? cleaned.slice(dot) : "";
52091
+ return cleaned.slice(0, 100 - ext.length) + ext;
52092
+ }
52079
52093
  function createLarkChatRouter(deps) {
52080
52094
  const timeoutMs = deps.turnTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS;
52081
52095
  const replyDelays = deps.replyRetryDelaysMs ?? [2e3, 8e3, 3e4];
52082
52096
  const sleep2 = deps.sleepImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
52097
+ const debounceMs = deps.debounceMs ?? DEBOUNCE_MS;
52098
+ const contentHoldMs = deps.contentHoldMs ?? CONTENT_HOLD_MS;
52099
+ const timerMsFor = (envs) => {
52100
+ if (envs.some((e) => e.text?.trim())) return debounceMs;
52101
+ const hasContent = envs.some(
52102
+ (e) => (e.imageKeys?.length ?? 0) > 0 || (e.files?.length ?? 0) > 0
52103
+ );
52104
+ return hasContent ? contentHoldMs : debounceMs;
52105
+ };
52083
52106
  const queues = /* @__PURE__ */ new Map();
52084
52107
  const activity = /* @__PURE__ */ new Map();
52085
- const runOne = async (env) => {
52086
- if (!env.messageId) {
52087
- deps.logger?.warn(`larkBot chat-router: message envelope missing messageId, drop ${env.eventId}`);
52088
- return;
52089
- }
52090
- const speaker = env.senderName || env.senderOpenId || "\u672A\u77E5\u6210\u5458";
52108
+ const pending = /* @__PURE__ */ new Map();
52109
+ const buildSection = async (env) => {
52091
52110
  const imageLines = [];
52092
52111
  for (const [i, key] of (env.imageKeys ?? []).entries()) {
52093
52112
  try {
52094
52113
  if (!deps.fetchResource || !deps.mediaRootFor) throw new Error("resource pipeline not wired");
52095
- const r = await deps.fetchResource(env.appId, env.messageId, key, MAX_IMAGE_BYTES);
52114
+ const r = await deps.fetchResource(env.appId, env.messageId, key, MAX_IMAGE_BYTES, "image");
52096
52115
  if (r.data.byteLength > MAX_IMAGE_BYTES) {
52097
52116
  deps.logger?.warn(`larkBot image oversize ${key}: ${r.data.byteLength}B`);
52098
52117
  imageLines.push(IMAGE_OVERSIZE_TEXT);
@@ -52114,25 +52133,69 @@ function createLarkChatRouter(deps) {
52114
52133
  imageLines.push(IMAGE_UNAVAILABLE_TEXT);
52115
52134
  }
52116
52135
  }
52117
- const rawBody = [env.text?.trim(), ...imageLines].filter(Boolean).join("\n");
52118
- const body = rawBody || "[\u975E\u6587\u672C\u6D88\u606F]";
52136
+ const fileLines = [];
52137
+ for (const [i, f] of (env.files ?? []).entries()) {
52138
+ try {
52139
+ if (!deps.fetchResource || !deps.mediaRootFor) throw new Error("resource pipeline not wired");
52140
+ const r = await deps.fetchResource(env.appId, env.messageId, f.key, MAX_FILE_BYTES, "file");
52141
+ if (r.data.byteLength > MAX_FILE_BYTES) {
52142
+ deps.logger?.warn(`larkBot file oversize ${f.key}: ${r.data.byteLength}B`);
52143
+ fileLines.push(FILE_OVERSIZE_TEXT(f.name));
52144
+ continue;
52145
+ }
52146
+ const dir = deps.mediaRootFor(env.chatId);
52147
+ import_node_fs27.default.mkdirSync(dir, { recursive: true });
52148
+ const p2 = import_node_path28.default.join(dir, `${safeMediaName(env.messageId)}-${i}-${safeAttachmentName(f.name)}`);
52149
+ import_node_fs27.default.writeFileSync(p2, r.data);
52150
+ fileLines.push(`[\u6587\u4EF6: ${p2}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
52151
+ } catch (err) {
52152
+ deps.logger?.warn(`larkBot file fetch failed ${f.key}: ${err.message}`);
52153
+ fileLines.push(FILE_UNAVAILABLE_TEXT(f.name));
52154
+ }
52155
+ }
52156
+ return [env.text?.trim(), ...imageLines, ...fileLines].filter(Boolean).join("\n");
52157
+ };
52158
+ const quoteAnnotation = (env, i, idxByMessageId) => {
52159
+ if (!env.parentMessageId) return void 0;
52160
+ const j = idxByMessageId.get(env.parentMessageId);
52161
+ if (j === void 0) return "[\u56DE\u590D\u66F4\u65E9\u7684\u4E00\u6761\u6D88\u606F]";
52162
+ if (j === i - 1) return void 0;
52163
+ return `[\u56DE\u590D\u7B2C${j + 1}\u6761]`;
52164
+ };
52165
+ const runBatch = async (batchEnvs) => {
52166
+ const envs = batchEnvs.filter((e) => {
52167
+ if (e.messageId) return true;
52168
+ deps.logger?.warn(`larkBot chat-router: message envelope missing messageId, drop ${e.eventId}`);
52169
+ return false;
52170
+ });
52171
+ if (envs.length === 0) return;
52172
+ const last = envs[envs.length - 1];
52173
+ const speaker = last.senderName || last.senderOpenId || "\u672A\u77E5\u6210\u5458";
52174
+ const idxByMessageId = new Map(envs.map((e, i) => [e.messageId, i]));
52175
+ const sections = [];
52176
+ for (const [i, env] of envs.entries()) {
52177
+ const body2 = await buildSection(env);
52178
+ const section = [quoteAnnotation(env, i, idxByMessageId), body2].filter(Boolean).join("\n");
52179
+ if (section) sections.push(section);
52180
+ }
52181
+ const body = sections.join("\n") || "[\u975E\u6587\u672C\u6D88\u606F]";
52119
52182
  const text = `${body}
52120
52183
 
52121
52184
  ${formatLarkSpeakerReminder(speaker)}`;
52122
52185
  const turn = deps.manager.runLarkChatTurn({
52123
- personaId: env.personaId,
52124
- chatId: env.chatId,
52125
- chatName: env.chatName,
52126
- chatType: env.chatType ?? "group",
52127
- senderOpenId: env.senderOpenId ?? "",
52128
- senderName: env.senderName ?? env.senderOpenId ?? "\u672A\u77E5\u6210\u5458",
52186
+ personaId: last.personaId,
52187
+ chatId: last.chatId,
52188
+ chatName: last.chatName,
52189
+ chatType: last.chatType ?? "group",
52190
+ senderOpenId: last.senderOpenId ?? "",
52191
+ senderName: last.senderName ?? last.senderOpenId ?? "\u672A\u77E5\u6210\u5458",
52129
52192
  text
52130
52193
  });
52131
- activity.set(env.chatId, {
52132
- chatId: env.chatId,
52133
- chatName: env.chatName,
52194
+ activity.set(last.chatId, {
52195
+ chatId: last.chatId,
52196
+ chatName: last.chatName,
52134
52197
  lastActiveAt: Date.now(),
52135
- personaId: env.personaId
52198
+ personaId: last.personaId
52136
52199
  });
52137
52200
  let timedOut = false;
52138
52201
  let timer;
@@ -52152,46 +52215,78 @@ ${formatLarkSpeakerReminder(speaker)}`;
52152
52215
  const replyText = "ok" in result && result.ok ? result.replyText : ERROR_REPLY_TEXT;
52153
52216
  if (!("ok" in result)) {
52154
52217
  deps.logger?.warn(
52155
- `larkBot turn ${timedOut ? "timeout" : "error"} (${env.chatId}): ${result.error}`
52218
+ `larkBot turn ${timedOut ? "timeout" : "error"} (${last.chatId}): ${result.error}`
52156
52219
  );
52157
52220
  }
52221
+ const absorbed = envs.slice(0, -1).map((e) => e.eventId);
52158
52222
  for (let attempt = 0; attempt <= replyDelays.length; attempt++) {
52159
52223
  if (attempt > 0) await sleep2(replyDelays[attempt - 1]);
52160
52224
  try {
52161
52225
  await deps.cloud.reply({
52162
- appId: env.appId,
52163
- chatId: env.chatId,
52164
- replyToMessageId: env.messageId,
52226
+ appId: last.appId,
52227
+ chatId: last.chatId,
52228
+ replyToMessageId: last.messageId,
52165
52229
  text: replyText,
52166
- eventId: env.eventId
52230
+ eventId: last.eventId,
52231
+ ...absorbed.length > 0 ? { absorbedEventIds: absorbed } : {}
52167
52232
  });
52168
52233
  return;
52169
52234
  } catch (err) {
52170
52235
  deps.logger?.warn(
52171
- `larkBot reply attempt ${attempt + 1} failed (${env.chatId}): ${err.message}`
52236
+ `larkBot reply attempt ${attempt + 1} failed (${last.chatId}): ${err.message}`
52172
52237
  );
52173
52238
  }
52174
52239
  }
52175
52240
  };
52241
+ const enqueueBatch = (batchEnvs) => {
52242
+ const chatId = batchEnvs[0].chatId;
52243
+ const prev = queues.get(chatId) ?? Promise.resolve();
52244
+ const next = prev.then(
52245
+ () => runBatch(batchEnvs).catch(
52246
+ (err) => deps.logger?.warn(`larkBot chat-router runBatch failed: ${err.message}`)
52247
+ )
52248
+ );
52249
+ queues.set(chatId, next);
52250
+ void next.finally(() => {
52251
+ if (queues.get(chatId) === next) queues.delete(chatId);
52252
+ });
52253
+ };
52254
+ const flushPending = (chatId) => {
52255
+ const batch = pending.get(chatId);
52256
+ if (!batch) return;
52257
+ clearTimeout(batch.timer);
52258
+ pending.delete(chatId);
52259
+ enqueueBatch(batch.envelopes);
52260
+ };
52176
52261
  return {
52177
52262
  handleEnvelope(env) {
52178
52263
  if (env.type === "bot_removed") {
52179
52264
  deps.store.addRemovedChat(env.personaId, env.chatId);
52180
52265
  activity.delete(env.chatId);
52181
52266
  queues.delete(env.chatId);
52267
+ const p2 = pending.get(env.chatId);
52268
+ if (p2) {
52269
+ clearTimeout(p2.timer);
52270
+ pending.delete(env.chatId);
52271
+ }
52182
52272
  deps.logger?.info(`larkBot removed from chat ${env.chatId} (persona ${env.personaId})`);
52183
52273
  return;
52184
52274
  }
52185
- const prev = queues.get(env.chatId) ?? Promise.resolve();
52186
- const next = prev.then(
52187
- () => runOne(env).catch(
52188
- (err) => deps.logger?.warn(`larkBot chat-router runOne failed: ${err.message}`)
52189
- )
52190
- );
52191
- queues.set(env.chatId, next);
52192
- void next.finally(() => {
52193
- if (queues.get(env.chatId) === next) queues.delete(env.chatId);
52194
- });
52275
+ const sender = env.senderOpenId ?? "";
52276
+ const cur = pending.get(env.chatId);
52277
+ if (cur && cur.senderOpenId !== sender) flushPending(env.chatId);
52278
+ const batch = pending.get(env.chatId);
52279
+ if (batch) {
52280
+ batch.envelopes.push(env);
52281
+ clearTimeout(batch.timer);
52282
+ batch.timer = setTimeout(() => flushPending(env.chatId), timerMsFor(batch.envelopes));
52283
+ } else {
52284
+ pending.set(env.chatId, {
52285
+ envelopes: [env],
52286
+ senderOpenId: sender,
52287
+ timer: setTimeout(() => flushPending(env.chatId), timerMsFor([env]))
52288
+ });
52289
+ }
52195
52290
  },
52196
52291
  listLarkSessions(personaId2) {
52197
52292
  return [...activity.values()].filter((a) => a.personaId === personaId2).map((a) => ({ chatId: a.chatId, chatName: a.chatName, lastActiveAt: a.lastActiveAt }));
@@ -52934,7 +53029,7 @@ var import_node_fs28 = __toESM(require("fs"), 1);
52934
53029
  var import_node_os8 = __toESM(require("os"), 1);
52935
53030
  var import_node_path30 = __toESM(require("path"), 1);
52936
53031
  init_protocol();
52937
- var MAX_FILE_BYTES = 2 * 1024 * 1024;
53032
+ var MAX_FILE_BYTES2 = 2 * 1024 * 1024;
52938
53033
  function resolveInsideCwd(cwd, subpath) {
52939
53034
  const absCwd = import_node_path30.default.resolve(cwd);
52940
53035
  const joined = import_node_path30.default.resolve(absCwd, subpath ?? ".");
@@ -52990,8 +53085,8 @@ var WorkspaceBrowser = class {
52990
53085
  if (!st.isFile()) {
52991
53086
  throw new ClawdError(ERROR_CODES.INVALID_PATH, `not a file: ${args.path}`);
52992
53087
  }
52993
- if (st.size > MAX_FILE_BYTES) {
52994
- throw new ClawdError(ERROR_CODES.FILE_TOO_LARGE, `file > ${MAX_FILE_BYTES} bytes`);
53088
+ if (st.size > MAX_FILE_BYTES2) {
53089
+ throw new ClawdError(ERROR_CODES.FILE_TOO_LARGE, `file > ${MAX_FILE_BYTES2} bytes`);
52995
53090
  }
52996
53091
  const buf = import_node_fs28.default.readFileSync(full);
52997
53092
  const isBinary = buf.includes(0);
@@ -58950,7 +59045,7 @@ function computeMethodAccess(args) {
58950
59045
  }
58951
59046
 
58952
59047
  // src/version.ts
58953
- var version = "0.2.298".length > 0 ? "0.2.298" : "dev";
59048
+ var version = "0.2.300".length > 0 ? "0.2.300" : "dev";
58954
59049
 
58955
59050
  // src/cli-probe/probe.ts
58956
59051
  var fs56 = __toESM(require("fs"), 1);
@@ -62968,7 +63063,7 @@ async function startDaemon(config) {
62968
63063
  manager,
62969
63064
  cloud: larkBotCloud,
62970
63065
  store: larkBotStore,
62971
- fetchResource: (appId, messageId, fileKey, maxBytes) => larkBotCloud.fetchResource(appId, messageId, fileKey, maxBytes),
63066
+ fetchResource: (appId, messageId, fileKey, maxBytes, type) => larkBotCloud.fetchResource(appId, messageId, fileKey, maxBytes, type),
62972
63067
  // 图片落 guest userWorkDir 下(capId = lark-<chat_id>):CC 经 --add-dir + allowRead carve 可 Read
62973
63068
  mediaRootFor: (chatId) => import_node_path65.default.join(deriveUserWorkDir(`lark-${chatId}`, usersRoot), "lark-media"),
62974
63069
  logger: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.298",
3
+ "version": "0.2.300",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",