@clawos-dev/clawd 0.2.298 → 0.2.299

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 +128 -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,7 @@ 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;
52066
52068
  var MAX_IMAGE_BYTES = 8 * 1024 * 1024;
52067
52069
  var EXT_BY_MIME = {
52068
52070
  "image/png": "png",
@@ -52072,27 +52074,35 @@ var EXT_BY_MIME = {
52072
52074
  };
52073
52075
  var IMAGE_OVERSIZE_TEXT = "[\u56FE\u7247\u6D88\u606F\uFF0C\u8D85\u51FA\u5927\u5C0F\u9650\u5236]";
52074
52076
  var IMAGE_UNAVAILABLE_TEXT = "[\u56FE\u7247\u6D88\u606F\uFF0C\u6682\u4E0D\u652F\u6301\u67E5\u770B]";
52077
+ var MAX_FILE_BYTES = 20 * 1024 * 1024;
52078
+ var FILE_OVERSIZE_TEXT = (name) => `[\u6587\u4EF6: ${name}\uFF0C\u8D85\u51FA\u5927\u5C0F\u9650\u5236]`;
52079
+ var FILE_UNAVAILABLE_TEXT = (name) => `[\u6587\u4EF6: ${name}\uFF0C\u62C9\u53D6\u5931\u8D25]`;
52075
52080
  function safeMediaName(raw) {
52076
52081
  const cleaned = raw.replace(/[^a-zA-Z0-9_\-.]/g, "_").replace(/^\.+/, (d) => "_".repeat(d.length));
52077
52082
  return (cleaned || "msg").replace(/\.\./g, "__");
52078
52083
  }
52084
+ function safeAttachmentName(raw) {
52085
+ const cleaned = raw.replace(/[/\\\u0000-\u001f]/g, "_").replace(/\.\./g, "__").replace(/^\.+/, (d) => "_".repeat(d.length));
52086
+ if (!cleaned) return "file";
52087
+ if (cleaned.length <= 100) return cleaned;
52088
+ const dot = cleaned.lastIndexOf(".");
52089
+ const ext = dot > 0 && cleaned.length - dot <= 20 ? cleaned.slice(dot) : "";
52090
+ return cleaned.slice(0, 100 - ext.length) + ext;
52091
+ }
52079
52092
  function createLarkChatRouter(deps) {
52080
52093
  const timeoutMs = deps.turnTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS;
52081
52094
  const replyDelays = deps.replyRetryDelaysMs ?? [2e3, 8e3, 3e4];
52082
52095
  const sleep2 = deps.sleepImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
52096
+ const debounceMs = deps.debounceMs ?? DEBOUNCE_MS;
52083
52097
  const queues = /* @__PURE__ */ new Map();
52084
52098
  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";
52099
+ const pending = /* @__PURE__ */ new Map();
52100
+ const buildSection = async (env) => {
52091
52101
  const imageLines = [];
52092
52102
  for (const [i, key] of (env.imageKeys ?? []).entries()) {
52093
52103
  try {
52094
52104
  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);
52105
+ const r = await deps.fetchResource(env.appId, env.messageId, key, MAX_IMAGE_BYTES, "image");
52096
52106
  if (r.data.byteLength > MAX_IMAGE_BYTES) {
52097
52107
  deps.logger?.warn(`larkBot image oversize ${key}: ${r.data.byteLength}B`);
52098
52108
  imageLines.push(IMAGE_OVERSIZE_TEXT);
@@ -52114,25 +52124,69 @@ function createLarkChatRouter(deps) {
52114
52124
  imageLines.push(IMAGE_UNAVAILABLE_TEXT);
52115
52125
  }
52116
52126
  }
52117
- const rawBody = [env.text?.trim(), ...imageLines].filter(Boolean).join("\n");
52118
- const body = rawBody || "[\u975E\u6587\u672C\u6D88\u606F]";
52127
+ const fileLines = [];
52128
+ for (const [i, f] of (env.files ?? []).entries()) {
52129
+ try {
52130
+ if (!deps.fetchResource || !deps.mediaRootFor) throw new Error("resource pipeline not wired");
52131
+ const r = await deps.fetchResource(env.appId, env.messageId, f.key, MAX_FILE_BYTES, "file");
52132
+ if (r.data.byteLength > MAX_FILE_BYTES) {
52133
+ deps.logger?.warn(`larkBot file oversize ${f.key}: ${r.data.byteLength}B`);
52134
+ fileLines.push(FILE_OVERSIZE_TEXT(f.name));
52135
+ continue;
52136
+ }
52137
+ const dir = deps.mediaRootFor(env.chatId);
52138
+ import_node_fs27.default.mkdirSync(dir, { recursive: true });
52139
+ const p2 = import_node_path28.default.join(dir, `${safeMediaName(env.messageId)}-${i}-${safeAttachmentName(f.name)}`);
52140
+ import_node_fs27.default.writeFileSync(p2, r.data);
52141
+ fileLines.push(`[\u6587\u4EF6: ${p2}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
52142
+ } catch (err) {
52143
+ deps.logger?.warn(`larkBot file fetch failed ${f.key}: ${err.message}`);
52144
+ fileLines.push(FILE_UNAVAILABLE_TEXT(f.name));
52145
+ }
52146
+ }
52147
+ return [env.text?.trim(), ...imageLines, ...fileLines].filter(Boolean).join("\n");
52148
+ };
52149
+ const quoteAnnotation = (env, i, idxByMessageId) => {
52150
+ if (!env.parentMessageId) return void 0;
52151
+ const j = idxByMessageId.get(env.parentMessageId);
52152
+ if (j === void 0) return "[\u56DE\u590D\u66F4\u65E9\u7684\u4E00\u6761\u6D88\u606F]";
52153
+ if (j === i - 1) return void 0;
52154
+ return `[\u56DE\u590D\u7B2C${j + 1}\u6761]`;
52155
+ };
52156
+ const runBatch = async (batchEnvs) => {
52157
+ const envs = batchEnvs.filter((e) => {
52158
+ if (e.messageId) return true;
52159
+ deps.logger?.warn(`larkBot chat-router: message envelope missing messageId, drop ${e.eventId}`);
52160
+ return false;
52161
+ });
52162
+ if (envs.length === 0) return;
52163
+ const last = envs[envs.length - 1];
52164
+ const speaker = last.senderName || last.senderOpenId || "\u672A\u77E5\u6210\u5458";
52165
+ const idxByMessageId = new Map(envs.map((e, i) => [e.messageId, i]));
52166
+ const sections = [];
52167
+ for (const [i, env] of envs.entries()) {
52168
+ const body2 = await buildSection(env);
52169
+ const section = [quoteAnnotation(env, i, idxByMessageId), body2].filter(Boolean).join("\n");
52170
+ if (section) sections.push(section);
52171
+ }
52172
+ const body = sections.join("\n") || "[\u975E\u6587\u672C\u6D88\u606F]";
52119
52173
  const text = `${body}
52120
52174
 
52121
52175
  ${formatLarkSpeakerReminder(speaker)}`;
52122
52176
  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",
52177
+ personaId: last.personaId,
52178
+ chatId: last.chatId,
52179
+ chatName: last.chatName,
52180
+ chatType: last.chatType ?? "group",
52181
+ senderOpenId: last.senderOpenId ?? "",
52182
+ senderName: last.senderName ?? last.senderOpenId ?? "\u672A\u77E5\u6210\u5458",
52129
52183
  text
52130
52184
  });
52131
- activity.set(env.chatId, {
52132
- chatId: env.chatId,
52133
- chatName: env.chatName,
52185
+ activity.set(last.chatId, {
52186
+ chatId: last.chatId,
52187
+ chatName: last.chatName,
52134
52188
  lastActiveAt: Date.now(),
52135
- personaId: env.personaId
52189
+ personaId: last.personaId
52136
52190
  });
52137
52191
  let timedOut = false;
52138
52192
  let timer;
@@ -52152,46 +52206,78 @@ ${formatLarkSpeakerReminder(speaker)}`;
52152
52206
  const replyText = "ok" in result && result.ok ? result.replyText : ERROR_REPLY_TEXT;
52153
52207
  if (!("ok" in result)) {
52154
52208
  deps.logger?.warn(
52155
- `larkBot turn ${timedOut ? "timeout" : "error"} (${env.chatId}): ${result.error}`
52209
+ `larkBot turn ${timedOut ? "timeout" : "error"} (${last.chatId}): ${result.error}`
52156
52210
  );
52157
52211
  }
52212
+ const absorbed = envs.slice(0, -1).map((e) => e.eventId);
52158
52213
  for (let attempt = 0; attempt <= replyDelays.length; attempt++) {
52159
52214
  if (attempt > 0) await sleep2(replyDelays[attempt - 1]);
52160
52215
  try {
52161
52216
  await deps.cloud.reply({
52162
- appId: env.appId,
52163
- chatId: env.chatId,
52164
- replyToMessageId: env.messageId,
52217
+ appId: last.appId,
52218
+ chatId: last.chatId,
52219
+ replyToMessageId: last.messageId,
52165
52220
  text: replyText,
52166
- eventId: env.eventId
52221
+ eventId: last.eventId,
52222
+ ...absorbed.length > 0 ? { absorbedEventIds: absorbed } : {}
52167
52223
  });
52168
52224
  return;
52169
52225
  } catch (err) {
52170
52226
  deps.logger?.warn(
52171
- `larkBot reply attempt ${attempt + 1} failed (${env.chatId}): ${err.message}`
52227
+ `larkBot reply attempt ${attempt + 1} failed (${last.chatId}): ${err.message}`
52172
52228
  );
52173
52229
  }
52174
52230
  }
52175
52231
  };
52232
+ const enqueueBatch = (batchEnvs) => {
52233
+ const chatId = batchEnvs[0].chatId;
52234
+ const prev = queues.get(chatId) ?? Promise.resolve();
52235
+ const next = prev.then(
52236
+ () => runBatch(batchEnvs).catch(
52237
+ (err) => deps.logger?.warn(`larkBot chat-router runBatch failed: ${err.message}`)
52238
+ )
52239
+ );
52240
+ queues.set(chatId, next);
52241
+ void next.finally(() => {
52242
+ if (queues.get(chatId) === next) queues.delete(chatId);
52243
+ });
52244
+ };
52245
+ const flushPending = (chatId) => {
52246
+ const batch = pending.get(chatId);
52247
+ if (!batch) return;
52248
+ clearTimeout(batch.timer);
52249
+ pending.delete(chatId);
52250
+ enqueueBatch(batch.envelopes);
52251
+ };
52176
52252
  return {
52177
52253
  handleEnvelope(env) {
52178
52254
  if (env.type === "bot_removed") {
52179
52255
  deps.store.addRemovedChat(env.personaId, env.chatId);
52180
52256
  activity.delete(env.chatId);
52181
52257
  queues.delete(env.chatId);
52258
+ const p2 = pending.get(env.chatId);
52259
+ if (p2) {
52260
+ clearTimeout(p2.timer);
52261
+ pending.delete(env.chatId);
52262
+ }
52182
52263
  deps.logger?.info(`larkBot removed from chat ${env.chatId} (persona ${env.personaId})`);
52183
52264
  return;
52184
52265
  }
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
- });
52266
+ const sender = env.senderOpenId ?? "";
52267
+ const cur = pending.get(env.chatId);
52268
+ if (cur && cur.senderOpenId !== sender) flushPending(env.chatId);
52269
+ const batch = pending.get(env.chatId);
52270
+ if (batch) {
52271
+ batch.envelopes.push(env);
52272
+ clearTimeout(batch.timer);
52273
+ batch.timer = setTimeout(() => flushPending(env.chatId), debounceMs);
52274
+ } else {
52275
+ pending.set(env.chatId, {
52276
+ envelopes: [env],
52277
+ senderOpenId: sender,
52278
+ timer: setTimeout(() => flushPending(env.chatId), debounceMs)
52279
+ });
52280
+ }
52195
52281
  },
52196
52282
  listLarkSessions(personaId2) {
52197
52283
  return [...activity.values()].filter((a) => a.personaId === personaId2).map((a) => ({ chatId: a.chatId, chatName: a.chatName, lastActiveAt: a.lastActiveAt }));
@@ -52934,7 +53020,7 @@ var import_node_fs28 = __toESM(require("fs"), 1);
52934
53020
  var import_node_os8 = __toESM(require("os"), 1);
52935
53021
  var import_node_path30 = __toESM(require("path"), 1);
52936
53022
  init_protocol();
52937
- var MAX_FILE_BYTES = 2 * 1024 * 1024;
53023
+ var MAX_FILE_BYTES2 = 2 * 1024 * 1024;
52938
53024
  function resolveInsideCwd(cwd, subpath) {
52939
53025
  const absCwd = import_node_path30.default.resolve(cwd);
52940
53026
  const joined = import_node_path30.default.resolve(absCwd, subpath ?? ".");
@@ -52990,8 +53076,8 @@ var WorkspaceBrowser = class {
52990
53076
  if (!st.isFile()) {
52991
53077
  throw new ClawdError(ERROR_CODES.INVALID_PATH, `not a file: ${args.path}`);
52992
53078
  }
52993
- if (st.size > MAX_FILE_BYTES) {
52994
- throw new ClawdError(ERROR_CODES.FILE_TOO_LARGE, `file > ${MAX_FILE_BYTES} bytes`);
53079
+ if (st.size > MAX_FILE_BYTES2) {
53080
+ throw new ClawdError(ERROR_CODES.FILE_TOO_LARGE, `file > ${MAX_FILE_BYTES2} bytes`);
52995
53081
  }
52996
53082
  const buf = import_node_fs28.default.readFileSync(full);
52997
53083
  const isBinary = buf.includes(0);
@@ -58950,7 +59036,7 @@ function computeMethodAccess(args) {
58950
59036
  }
58951
59037
 
58952
59038
  // src/version.ts
58953
- var version = "0.2.298".length > 0 ? "0.2.298" : "dev";
59039
+ var version = "0.2.299".length > 0 ? "0.2.299" : "dev";
58954
59040
 
58955
59041
  // src/cli-probe/probe.ts
58956
59042
  var fs56 = __toESM(require("fs"), 1);
@@ -62968,7 +63054,7 @@ async function startDaemon(config) {
62968
63054
  manager,
62969
63055
  cloud: larkBotCloud,
62970
63056
  store: larkBotStore,
62971
- fetchResource: (appId, messageId, fileKey, maxBytes) => larkBotCloud.fetchResource(appId, messageId, fileKey, maxBytes),
63057
+ fetchResource: (appId, messageId, fileKey, maxBytes, type) => larkBotCloud.fetchResource(appId, messageId, fileKey, maxBytes, type),
62972
63058
  // 图片落 guest userWorkDir 下(capId = lark-<chat_id>):CC 经 --add-dir + allowRead carve 可 Read
62973
63059
  mediaRootFor: (chatId) => import_node_path65.default.join(deriveUserWorkDir(`lark-${chatId}`, usersRoot), "lark-media"),
62974
63060
  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.299",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",