@clawos-dev/clawd 0.2.762 → 0.2.763

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 +157 -21
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -47897,6 +47897,115 @@ function extractLarkReplyText(events) {
47897
47897
  return order.map((k3) => (chunks.get(k3) ?? []).join("")).filter((t2) => t2.length > 0).join("\n").trim();
47898
47898
  }
47899
47899
 
47900
+ // src/session/lark-tool-send.ts
47901
+ var LARK_RESUME_MARKER = "\u300C\u7EE7\u7EED\u300D\u7531\u7CFB\u7EDF\u4EE3\u53D1";
47902
+ var isResumePrompt = (text) => text.includes(LARK_RESUME_MARKER);
47903
+ var SEND_COMMAND_RES = [
47904
+ /\bim\s+\+messages-send\b/,
47905
+ /\bim\s+messages\s+(?:create|reply)\b/,
47906
+ /\bapi\s+POST\s+\/open-apis\/im\/v1\/messages(?:[\/\s'"`?#]|$)/
47907
+ ];
47908
+ var DRY_RUN_RE = /--dry-run\b|\bdry_run\b/;
47909
+ var TARGET_CHAT_RES = [
47910
+ /--chat-id[= ]+['"]?(oc_[A-Za-z0-9_-]+)/,
47911
+ /--receive-id[= ]+['"]?(oc_[A-Za-z0-9_-]+)/,
47912
+ /"receive_id"\s*:\s*"(oc_[A-Za-z0-9_-]+)"/,
47913
+ /'receive_id'\s*:\s*'(oc_[A-Za-z0-9_-]+)'/
47914
+ ];
47915
+ var FAILED_CODE_RE = /"code"\s*:\s*[1-9]/;
47916
+ var OK_CODE_RE = /"code"\s*:\s*0\b/;
47917
+ var MESSAGE_ID_RE = /"?(?:message_id|messageId)"?\s*[:=]\s*"?om_[A-Za-z0-9_-]+/;
47918
+ var textOf = (value) => {
47919
+ if (typeof value === "string") return value;
47920
+ if (value == null) return "";
47921
+ try {
47922
+ return JSON.stringify(value);
47923
+ } catch {
47924
+ return "";
47925
+ }
47926
+ };
47927
+ var commandOf = (input) => {
47928
+ if (typeof input === "string") return input;
47929
+ if (!input || typeof input !== "object") return "";
47930
+ const parts = [];
47931
+ const walk2 = (v3, depth) => {
47932
+ if (depth > 4 || v3 == null) return;
47933
+ if (typeof v3 === "string") parts.push(v3);
47934
+ else if (Array.isArray(v3)) for (const item of v3) walk2(item, depth + 1);
47935
+ else if (typeof v3 === "object") for (const item of Object.values(v3)) walk2(item, depth + 1);
47936
+ };
47937
+ walk2(input, 0);
47938
+ return parts.join("\n");
47939
+ };
47940
+ var targetChatOf = (command) => {
47941
+ for (const re2 of TARGET_CHAT_RES) {
47942
+ const m3 = re2.exec(command);
47943
+ if (m3) return m3[1];
47944
+ }
47945
+ return void 0;
47946
+ };
47947
+ var targetChatInOutput = (output) => {
47948
+ const text = textOf(output);
47949
+ const m3 = /"?chat_id"?\s*:\s*"?(oc_[A-Za-z0-9_-]+)/.exec(text);
47950
+ return m3?.[1];
47951
+ };
47952
+ var isSendCommand = (command) => command.includes("lark-cli") && !DRY_RUN_RE.test(command) && SEND_COMMAND_RES.some((re2) => re2.test(command));
47953
+ var isSentResult = (output, error) => {
47954
+ if (error != null) return false;
47955
+ const text = textOf(output);
47956
+ if (!text) return false;
47957
+ if (FAILED_CODE_RE.test(text)) return false;
47958
+ return OK_CODE_RE.test(text) || MESSAGE_ID_RE.test(text);
47959
+ };
47960
+ var realChatIdOf = (chatId) => chatId.split(":")[0] ?? chatId;
47961
+ function detectLarkToolSend(events, chatId) {
47962
+ const expected = realChatIdOf(chatId);
47963
+ if (!expected) return false;
47964
+ let start = 0;
47965
+ for (let i2 = events.length - 1; i2 >= 0; i2--) {
47966
+ const ev = events[i2];
47967
+ if (ev.kind !== "user_text") continue;
47968
+ if (!isResumePrompt(ev.text)) {
47969
+ start = i2 + 1;
47970
+ break;
47971
+ }
47972
+ }
47973
+ const calls = /* @__PURE__ */ new Map();
47974
+ for (const ev of events.slice(start)) {
47975
+ if (ev.kind === "tool_call") {
47976
+ const command2 = commandOf(ev.input);
47977
+ if (isSendCommand(command2)) calls.set(ev.toolUseId, command2);
47978
+ continue;
47979
+ }
47980
+ if (ev.kind !== "tool_result") continue;
47981
+ const command = calls.get(ev.toolUseId);
47982
+ if (!command) continue;
47983
+ if (!isSentResult(ev.output, ev.error)) continue;
47984
+ if (targetChatOf(command) === expected) return true;
47985
+ if (targetChatInOutput(ev.output) === expected) return true;
47986
+ }
47987
+ return false;
47988
+ }
47989
+ function createLarkSendWatch(chatId) {
47990
+ const expected = realChatIdOf(chatId);
47991
+ let fired = false;
47992
+ const calls = /* @__PURE__ */ new Map();
47993
+ return (event) => {
47994
+ if (fired || !expected) return fired;
47995
+ if (event.kind === "tool_call") {
47996
+ const command2 = commandOf(event.input);
47997
+ if (isSendCommand(command2)) calls.set(event.toolUseId, command2);
47998
+ return fired;
47999
+ }
48000
+ if (event.kind !== "tool_result") return fired;
48001
+ const command = calls.get(event.toolUseId);
48002
+ if (!command) return fired;
48003
+ if (!isSentResult(event.output, event.error)) return fired;
48004
+ if (targetChatOf(command) === expected || targetChatInOutput(event.output) === expected) fired = true;
48005
+ return fired;
48006
+ };
48007
+ }
48008
+
47900
48009
  // src/session/runner.ts
47901
48010
  var import_node_os9 = __toESM(require("os"), 1);
47902
48011
  var import_node_path15 = __toESM(require("path"), 1);
@@ -51703,29 +51812,49 @@ var SessionManager = class {
51703
51812
  r2.input({ kind: "command", command: { kind: "stop" } });
51704
51813
  };
51705
51814
  const settleDelayMs = args.settleDelayMs ?? (this.deps.mode === "tui" ? 500 : 0);
51815
+ const sendWatch = createLarkSendWatch(args.chatId);
51816
+ let sentDuringTurn = false;
51817
+ const observeFrame = (frame) => {
51818
+ if (sentDuringTurn || frame?.type !== "session:event" || !frame.event) return;
51819
+ if (!sendWatch(frame.event)) return;
51820
+ sentDuringTurn = true;
51821
+ };
51822
+ const repliedByToolOf = (events) => sentDuringTurn || detectLarkToolSend(events, args.chatId);
51706
51823
  const extractAndSettle = () => {
51707
51824
  try {
51708
51825
  const events = this.readHistoryEventsForScope(sessionId, scope);
51709
51826
  const replyText = extractLarkReplyText(events);
51710
- settle(replyText ? { ok: true, replyText } : { error: "empty reply" });
51827
+ const repliedByTool = repliedByToolOf(events);
51828
+ settle(replyText ? { ok: true, replyText, ...repliedByTool ? { repliedByTool: true } : {} } : repliedByTool ? { ok: true, replyText: "", repliedByTool: true } : { error: "empty reply" });
51711
51829
  } catch (err) {
51712
51830
  settle({ error: err.message });
51713
51831
  }
51714
51832
  };
51715
- unsubscribe = this.subscribe(
51716
- sessionId,
51717
- scope,
51718
- makeEndFrameHandler(sessionId, (info) => {
51719
- if (info.kind === "idle") {
51720
- if (settleDelayMs === 0) extractAndSettle();
51721
- else setTimeout(extractAndSettle, settleDelayMs);
51722
- } else if (info.kind === "stopped") {
51723
- settle({ error: "session stopped before reply" });
51724
- } else {
51725
- settle({ error: info.message || "session error" });
51726
- }
51727
- })
51728
- );
51833
+ const sentByTool = () => {
51834
+ if (sentDuringTurn) return true;
51835
+ try {
51836
+ return detectLarkToolSend(this.readHistoryEventsForScope(sessionId, scope), args.chatId);
51837
+ } catch {
51838
+ return false;
51839
+ }
51840
+ };
51841
+ const settleError = (message) => {
51842
+ settle(sentByTool() ? { ok: true, replyText: "", repliedByTool: true } : { error: message });
51843
+ };
51844
+ const onEndFrame = makeEndFrameHandler(sessionId, (info) => {
51845
+ if (info.kind === "idle") {
51846
+ if (settleDelayMs === 0) extractAndSettle();
51847
+ else setTimeout(extractAndSettle, settleDelayMs);
51848
+ } else if (info.kind === "stopped") {
51849
+ settleError("session stopped before reply");
51850
+ } else {
51851
+ settleError(info.message || "session error");
51852
+ }
51853
+ });
51854
+ unsubscribe = this.subscribe(sessionId, scope, (frame) => {
51855
+ observeFrame(frame);
51856
+ onEndFrame(frame);
51857
+ });
51729
51858
  const runner = this.ensureRunnerForScope(file, scope);
51730
51859
  runner.input({
51731
51860
  kind: "command",
@@ -57371,7 +57500,7 @@ function ledgerOf(t2) {
57371
57500
  function stateOf(t2) {
57372
57501
  return t2.status.state.replace(/^TASK_STATE_/, "").toLowerCase();
57373
57502
  }
57374
- var textOf = (m3) => (m3?.parts ?? []).map((p3) => typeof p3.text === "string" ? p3.text : "").filter(Boolean).join("\n");
57503
+ var textOf2 = (m3) => (m3?.parts ?? []).map((p3) => typeof p3.text === "string" ? p3.text : "").filter(Boolean).join("\n");
57375
57504
  function firstUserMessage(t2) {
57376
57505
  return t2.history?.find((m3) => m3.role === "ROLE_USER");
57377
57506
  }
@@ -57407,9 +57536,9 @@ function toTaskView(t2, workerSessionId) {
57407
57536
  executorThreadId: l2.executor.threadId,
57408
57537
  ...workerSessionId ? { workerSessionId } : {},
57409
57538
  ...sessionLabel ? { sessionLabel } : {},
57410
- taskText: textOf(firstUserMessage(t2)),
57539
+ taskText: textOf2(firstUserMessage(t2)),
57411
57540
  brief: typeof brief === "string" && brief ? brief : null,
57412
- resultText: (resultArtifact ? textOf({ messageId: "", role: "ROLE_AGENT", parts: resultArtifact.parts }) : "") || textOf(lastAgent) || null,
57541
+ resultText: (resultArtifact ? textOf2({ messageId: "", role: "ROLE_AGENT", parts: resultArtifact.parts }) : "") || textOf2(lastAgent) || null,
57413
57542
  filePaths: Array.isArray(filePaths) ? filePaths.filter((p3) => typeof p3 === "string") : [],
57414
57543
  line: t2.metadata[A2A_LINE_EXTENSION] ?? {},
57415
57544
  deliveredAt: l2.deliveredAt,
@@ -57441,7 +57570,7 @@ You may read this file with your Read tool to understand context. You do not hav
57441
57570
  const detail = `${head}
57442
57571
 
57443
57572
  Task:
57444
- ${textOf(msg)}${source}
57573
+ ${textOf2(msg)}${source}
57445
57574
 
57446
57575
  When done, call \`a2aReport\` from the \`clawd-a2a\` MCP server (e.g. \`mcp__clawd-a2a__a2aReport\` in claude):
57447
57576
  - done: { state: "completed", text, brief, filePaths? }
@@ -67536,6 +67665,11 @@ ${formatLarkSpeakerReminder(speaker)}`
67536
67665
  deps.logger?.warn(`larkBot turn error (${last.chatId}): ${result.error}`);
67537
67666
  }
67538
67667
  const absorbed = envs.slice(0, -1).map((e) => e.eventId);
67668
+ const repliedByTool = "ok" in result && result.repliedByTool === true;
67669
+ if (repliedByTool) {
67670
+ deps.logger?.info(`larkBot already replied by tool (${last.chatId}), suppressing auto reply`);
67671
+ }
67672
+ const replyBody = repliedByTool ? replyText || "[replied-by-tool]" : replyText;
67539
67673
  for (let attempt = 0; attempt <= replyDelays.length; attempt++) {
67540
67674
  if (attempt > 0) await sleep2(replyDelays[attempt - 1]);
67541
67675
  try {
@@ -67544,8 +67678,10 @@ ${formatLarkSpeakerReminder(speaker)}`
67544
67678
  // 回复走真身:chatId 自 spec 2026-09-10 起是虚拟三段式,只作会话 key 用
67545
67679
  chatId: last.realChatId ?? last.chatId,
67546
67680
  replyToMessageId: replyAnchorOf(last),
67547
- text: replyText,
67681
+ text: replyBody,
67548
67682
  eventId: last.eventId,
67683
+ // 工具已代发:中心只销账不发送(台账仍要关,否则重启认领会重跑同一事件)
67684
+ ...repliedByTool ? { repliedByTool: true } : {},
67549
67685
  // 文档评论:云端据此走评论回复接口而非 IM(显式字段,不靠 chatId 前缀猜)
67550
67686
  ...last.type === "doc_comment" ? { docComment: { fileToken: last.fileToken, fileType: last.fileType ?? "docx" } } : {},
67551
67687
  ...absorbed.length > 0 ? { absorbedEventIds: absorbed } : {}
@@ -76541,7 +76677,7 @@ function computeMethodAccess(args) {
76541
76677
  }
76542
76678
 
76543
76679
  // src/version.ts
76544
- var version = "0.2.762".length > 0 ? "0.2.762" : "dev";
76680
+ var version = "0.2.763".length > 0 ? "0.2.763" : "dev";
76545
76681
 
76546
76682
  // src/cli-probe/probe.ts
76547
76683
  var fs87 = __toESM(require("fs"), 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.762",
3
+ "version": "0.2.763",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",