@ai-setting/roy-agent-cli 1.7.57 → 1.7.58

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.
@@ -9639,7 +9639,7 @@ class CliQuietModeService {
9639
9639
  // package.json
9640
9640
  var package_default = {
9641
9641
  name: "@ai-setting/roy-agent-cli",
9642
- version: "1.7.57",
9642
+ version: "1.7.58",
9643
9643
  type: "module",
9644
9644
  description: "CLI for roy-agent - Non-interactive command execution",
9645
9645
  main: "./dist/index.js",
@@ -11785,6 +11785,262 @@ import {
11785
11785
  } from "@ai-setting/roy-agent-core/env";
11786
11786
  import { TracedAs as TracedAs5 } from "@ai-setting/roy-agent-core";
11787
11787
  import { notifyReactFailure as notifyReactFailure2 } from "@ai-setting/roy-agent-core/env/error-reply";
11788
+
11789
+ // src/commands/shared/telemetry-notice.ts
11790
+ import { spawn } from "node:child_process";
11791
+ import { normalizeLarkAtMentions } from "@ai-setting/roy-agent-core/env/lark-mention-utils";
11792
+ import { deriveIdempotencyKey } from "@ai-setting/roy-agent-core/env/event-source/idempotency-key";
11793
+ var TELEMETRY_EVENT_TYPES = new Set([
11794
+ "task.background.started",
11795
+ "task.background.progress"
11796
+ ]);
11797
+ function isTelemetryEvent(type) {
11798
+ if (!type)
11799
+ return false;
11800
+ return TELEMETRY_EVENT_TYPES.has(type);
11801
+ }
11802
+ var MAX_TITLE_CHARS = 40;
11803
+ function pad2(n) {
11804
+ return n < 10 ? `0${n}` : String(n);
11805
+ }
11806
+ function formatElapsed(ms) {
11807
+ if (!Number.isFinite(ms) || ms <= 0)
11808
+ return "0m";
11809
+ const totalMin = Math.floor(ms / 60000);
11810
+ if (totalMin < 60)
11811
+ return `${totalMin}m`;
11812
+ const totalHour = Math.floor(totalMin / 60);
11813
+ const min = totalMin % 60;
11814
+ if (totalHour < 24) {
11815
+ return min === 0 ? `${totalHour}h` : `${totalHour}h${min}m`;
11816
+ }
11817
+ const day = Math.floor(totalHour / 24);
11818
+ const hour = totalHour % 24;
11819
+ return hour === 0 ? `${day}d` : `${day}d${hour}h`;
11820
+ }
11821
+ function formatClock(ts) {
11822
+ const d = new Date(ts);
11823
+ return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
11824
+ }
11825
+ function formatTimeoutLabel(ms) {
11826
+ if (!Number.isFinite(ms) || ms <= 0)
11827
+ return "0m";
11828
+ return formatElapsed(ms);
11829
+ }
11830
+ function truncateTitle(raw) {
11831
+ const s = raw.trim();
11832
+ if (s.length <= MAX_TITLE_CHARS)
11833
+ return s;
11834
+ return `${s.slice(0, MAX_TITLE_CHARS)}…`;
11835
+ }
11836
+ function pickString(v) {
11837
+ return typeof v === "string" && v.length > 0 ? v : undefined;
11838
+ }
11839
+ function pickNumber(v) {
11840
+ return typeof v === "number" && Number.isFinite(v) ? v : undefined;
11841
+ }
11842
+ function buildTelemetryNoticeText(type, payload, now = Date.now()) {
11843
+ const p = payload ?? {};
11844
+ const taskId = pickNumber(p.associatedTaskId);
11845
+ const bgId = pickString(p.backgroundTaskId);
11846
+ const desc = truncateTitle(pickString(p.description) ?? "后台任务");
11847
+ let label;
11848
+ if (taskId !== undefined)
11849
+ label = `Task #${taskId}`;
11850
+ else if (bgId)
11851
+ label = bgId;
11852
+ else
11853
+ label = "后台任务";
11854
+ const head = type === "task.background.started" ? `\uD83D\uDD14 ${label}「${desc}」已启动` : `\uD83D\uDD04 ${label}「${desc}」后台运行中`;
11855
+ const parts = [];
11856
+ const startedAt = pickNumber(p.startedAt);
11857
+ const timeoutMs = pickNumber(p.timeoutMs);
11858
+ if (type !== "task.background.started" && startedAt !== undefined) {
11859
+ parts.push(`已跑 ${formatElapsed(now - startedAt)}`);
11860
+ }
11861
+ if (startedAt !== undefined && timeoutMs !== undefined) {
11862
+ parts.push(`预计 ${formatClock(startedAt + timeoutMs)} 超时(timeout ${formatTimeoutLabel(timeoutMs)})`);
11863
+ }
11864
+ let text = head;
11865
+ if (parts.length > 0)
11866
+ text += `
11867
+ ${parts.join(",")}`;
11868
+ const progressMessage = pickString(p.progressMessage);
11869
+ if (progressMessage)
11870
+ text += `
11871
+ ${progressMessage}`;
11872
+ return text;
11873
+ }
11874
+ var DEFAULT_SEND_TIMEOUT_MS = 5000;
11875
+ var MAX_IDEMPOTENCY_KEY_LEN = 50;
11876
+ function deriveTelemetryIdempotencyKey(eventId, chatId) {
11877
+ const anchor = pickString(eventId) ?? `ts-${Date.now()}-${Math.random()}`;
11878
+ return deriveIdempotencyKey(["telemetry", anchor, chatId]);
11879
+ }
11880
+ function buildLarkSendArgs(input) {
11881
+ const { chatId, text, profile, idempotencyKey } = input;
11882
+ const args = [
11883
+ "im",
11884
+ "+messages-send",
11885
+ "--chat-id",
11886
+ chatId,
11887
+ "--markdown",
11888
+ normalizeLarkAtMentions(text),
11889
+ "--as",
11890
+ "bot"
11891
+ ];
11892
+ if (idempotencyKey) {
11893
+ args.push("--idempotency-key", idempotencyKey.slice(0, MAX_IDEMPOTENCY_KEY_LEN));
11894
+ }
11895
+ if (profile)
11896
+ args.unshift("--profile", profile);
11897
+ return args;
11898
+ }
11899
+ function sendLarkChatMarkdown(options) {
11900
+ const { chatId, text, profile, idempotencyKey } = options;
11901
+ const timeoutMs = options.timeoutMs ?? DEFAULT_SEND_TIMEOUT_MS;
11902
+ if (!chatId) {
11903
+ return Promise.reject(new Error("sendLarkChatMarkdown: empty chatId"));
11904
+ }
11905
+ return new Promise((resolve, reject) => {
11906
+ let settled = false;
11907
+ let timer;
11908
+ const finish = (err) => {
11909
+ if (settled)
11910
+ return;
11911
+ settled = true;
11912
+ if (timer)
11913
+ clearTimeout(timer);
11914
+ if (err)
11915
+ reject(err);
11916
+ else
11917
+ resolve();
11918
+ };
11919
+ const args = buildLarkSendArgs({ chatId, text, profile, idempotencyKey });
11920
+ let proc;
11921
+ try {
11922
+ proc = spawn("lark-cli", args, {
11923
+ shell: false,
11924
+ stdio: ["ignore", "pipe", "pipe"]
11925
+ });
11926
+ } catch (err) {
11927
+ finish(err instanceof Error ? err : new Error(String(err)));
11928
+ return;
11929
+ }
11930
+ let stderr = "";
11931
+ proc.stderr?.on("data", (chunk) => {
11932
+ stderr += chunk.toString();
11933
+ });
11934
+ timer = setTimeout(() => {
11935
+ try {
11936
+ proc.kill("SIGKILL");
11937
+ } catch {}
11938
+ finish(new Error(`lark-cli timed out after ${timeoutMs}ms`));
11939
+ }, timeoutMs);
11940
+ timer.unref?.();
11941
+ proc.on("close", (code) => {
11942
+ if (code === 0)
11943
+ finish();
11944
+ else
11945
+ finish(new Error(`lark-cli exited with code ${code}: ${stderr}`));
11946
+ });
11947
+ proc.on("error", (err) => finish(err));
11948
+ });
11949
+ }
11950
+ function targetFromEventMetadata(metadata) {
11951
+ if (!metadata)
11952
+ return;
11953
+ const replyChannel = metadata.replyChannel;
11954
+ const chatId = pickString(replyChannel?.chatId) ?? pickString(metadata.chatId);
11955
+ if (!chatId)
11956
+ return;
11957
+ const profile = pickString(replyChannel?.profile) ?? pickString(metadata.profile);
11958
+ return profile ? { chatId, profile } : { chatId };
11959
+ }
11960
+ async function targetFromTriggerEvent(eventStore, triggerEventId) {
11961
+ if (!eventStore || !triggerEventId)
11962
+ return;
11963
+ let persisted;
11964
+ try {
11965
+ persisted = await eventStore.getEventById(triggerEventId);
11966
+ } catch {
11967
+ return;
11968
+ }
11969
+ if (!persisted || typeof persisted !== "object")
11970
+ return;
11971
+ const metadata = persisted.metadata;
11972
+ if (!metadata || typeof metadata !== "object")
11973
+ return;
11974
+ return targetFromEventMetadata(metadata);
11975
+ }
11976
+
11977
+ // src/commands/shared/self-echo-filter.ts
11978
+ var BOT_SENDER_TYPES = new Set(["bot", "app"]);
11979
+ function pickString2(v) {
11980
+ return typeof v === "string" && v.length > 0 ? v : undefined;
11981
+ }
11982
+ function extractSenderIdentity(event) {
11983
+ if (!event)
11984
+ return {};
11985
+ const metadata = event.metadata;
11986
+ const payload = event.payload;
11987
+ const payloadMeta = payload?.metadata;
11988
+ const rawEvent = payload?.rawEvent;
11989
+ const innerEvent = rawEvent?.event;
11990
+ const innerSender = innerEvent?.sender;
11991
+ const topSender = rawEvent?.sender;
11992
+ const senderType = pickString2(metadata?.senderType) ?? pickString2(payloadMeta?.senderType) ?? pickString2(rawEvent?.sender_type) ?? pickString2(innerSender?.sender_type) ?? pickString2(topSender?.sender_type) ?? pickString2(payload?.senderType);
11993
+ const nestedSenderId = innerSender?.sender_id ?? topSender?.sender_id;
11994
+ const senderId = pickString2(metadata?.senderId) ?? pickString2(payloadMeta?.senderId) ?? pickString2(payload?.senderId) ?? pickString2(rawEvent?.sender_id) ?? (typeof nestedSenderId === "string" ? pickString2(nestedSenderId) : pickString2(nestedSenderId?.open_id) ?? pickString2(nestedSenderId?.user_id) ?? pickString2(nestedSenderId?.union_id));
11995
+ return {
11996
+ ...senderType ? { senderType } : {},
11997
+ ...senderId ? { senderId } : {}
11998
+ };
11999
+ }
12000
+ function isSelfEchoEvent(event, options = {}) {
12001
+ const { senderType, senderId } = extractSenderIdentity(event);
12002
+ if (options.enabled === false) {
12003
+ return {
12004
+ skip: false,
12005
+ reason: "disabled",
12006
+ ...senderType ? { senderType } : {},
12007
+ ...senderId ? { senderId } : {}
12008
+ };
12009
+ }
12010
+ if (senderType && BOT_SENDER_TYPES.has(senderType.toLowerCase())) {
12011
+ return {
12012
+ skip: true,
12013
+ reason: "bot-sender",
12014
+ ...senderType ? { senderType } : {},
12015
+ ...senderId ? { senderId } : {}
12016
+ };
12017
+ }
12018
+ if (senderId && options.selfBotIds && options.selfBotIds.length > 0) {
12019
+ if (options.selfBotIds.includes(senderId)) {
12020
+ return {
12021
+ skip: true,
12022
+ reason: "self-bot-id",
12023
+ ...senderType ? { senderType } : {},
12024
+ senderId
12025
+ };
12026
+ }
12027
+ }
12028
+ return {
12029
+ skip: false,
12030
+ ...senderType ? { senderType } : {},
12031
+ ...senderId ? { senderId } : {}
12032
+ };
12033
+ }
12034
+ var SELF_ECHO_FILTER_ENABLED_ENV = "ROY_AGENT_SELF_ECHO_FILTER";
12035
+ function readSelfEchoFilterEnabled(env = process.env) {
12036
+ const raw = env[SELF_ECHO_FILTER_ENABLED_ENV];
12037
+ if (raw === undefined)
12038
+ return true;
12039
+ const v = raw.trim().toLowerCase();
12040
+ return !(v === "0" || v === "false" || v === "off" || v === "no");
12041
+ }
12042
+
12043
+ // src/commands/shared/event-handler.ts
11788
12044
  function parseLarkPerTaskChatId(contextJson) {
11789
12045
  if (!contextJson)
11790
12046
  return;
@@ -11837,6 +12093,15 @@ class EventHandler {
11837
12093
  resolvedSessionByTrigger = new Map;
11838
12094
  eventStore = null;
11839
12095
  getHandlerForEsType = null;
12096
+ telemetrySender = (notice) => sendLarkChatMarkdown(notice);
12097
+ selfEchoFilter = {
12098
+ enabled: true,
12099
+ selfBotIds: []
12100
+ };
12101
+ selfEchoDroppedCount = 0;
12102
+ getSelfEchoDroppedCount() {
12103
+ return this.selfEchoDroppedCount;
12104
+ }
11840
12105
  constructor(options) {
11841
12106
  this.env = options.env;
11842
12107
  this.eventTypes = options.eventTypes;
@@ -11851,6 +12116,11 @@ class EventHandler {
11851
12116
  this.sessionComponent = options.sessionComponent ?? null;
11852
12117
  this.eventStore = options.eventStore ?? null;
11853
12118
  this.getHandlerForEsType = options.getHandlerForEsType ?? null;
12119
+ this.telemetrySender = options.telemetrySender ?? ((notice) => sendLarkChatMarkdown(notice));
12120
+ this.selfEchoFilter = {
12121
+ enabled: options.selfEchoFilter?.enabled ?? readSelfEchoFilterEnabled(),
12122
+ selfBotIds: options.selfEchoFilter?.selfBotIds ?? []
12123
+ };
11854
12124
  }
11855
12125
  start() {
11856
12126
  this.isStopped = false;
@@ -11897,9 +12167,45 @@ class EventHandler {
11897
12167
  if (this.isStopped) {
11898
12168
  return;
11899
12169
  }
12170
+ if (this.selfEchoFilter.enabled) {
12171
+ const verdict = isSelfEchoEvent(event, {
12172
+ selfBotIds: this.selfEchoFilter.selfBotIds
12173
+ });
12174
+ if (verdict.skip) {
12175
+ this.selfEchoDroppedCount += 1;
12176
+ console.error(`[EventHandler] self-echo event dropped (reason=${verdict.reason}` + `, senderType=${verdict.senderType ?? "-"}` + `, senderId=${verdict.senderId ?? "-"}` + `, type=${event.type})`);
12177
+ return;
12178
+ }
12179
+ }
12180
+ if (isTelemetryEvent(event.type)) {
12181
+ this.sendTelemetryNotice(event);
12182
+ return;
12183
+ }
11900
12184
  const formatted = await this.formatEvent(event);
11901
12185
  this.enqueue(formatted);
11902
12186
  }
12187
+ async sendTelemetryNotice(event) {
12188
+ try {
12189
+ const metadata = event.metadata;
12190
+ let target = targetFromEventMetadata(metadata);
12191
+ if (!target) {
12192
+ const triggerEventId = typeof metadata?.triggerEventId === "string" ? metadata.triggerEventId : undefined;
12193
+ target = await targetFromTriggerEvent(this.eventStore, triggerEventId);
12194
+ }
12195
+ if (!target) {
12196
+ return;
12197
+ }
12198
+ const text = buildTelemetryNoticeText(event.type, event.payload);
12199
+ await this.telemetrySender({
12200
+ chatId: target.chatId,
12201
+ ...target.profile ? { profile: target.profile } : {},
12202
+ text,
12203
+ idempotencyKey: deriveTelemetryIdempotencyKey(event.id, target.chatId)
12204
+ });
12205
+ } catch (err) {
12206
+ console.error(`[EventHandler] telemetry notice failed (swallowed) type=${event.type}`, err);
12207
+ }
12208
+ }
11903
12209
  async formatEvent(event) {
11904
12210
  if (NOTIFY_V2_TERMINAL_TYPES.has(event.type) && event.metadata?.triggerEventId && this.eventStore && this.getHandlerForEsType) {
11905
12211
  try {
@@ -12387,6 +12693,14 @@ __legacyDecorateClassTS([
12387
12693
  ]),
12388
12694
  __legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
12389
12695
  ], EventHandler.prototype, "handleEvent", null);
12696
+ __legacyDecorateClassTS([
12697
+ TracedAs5("event_handler.telemetry_notice", { recordParams: true, recordResult: false }),
12698
+ __legacyMetadataTS("design:type", Function),
12699
+ __legacyMetadataTS("design:paramtypes", [
12700
+ typeof EnvEvent === "undefined" ? Object : EnvEvent
12701
+ ]),
12702
+ __legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
12703
+ ], EventHandler.prototype, "sendTelemetryNotice", null);
12390
12704
  __legacyDecorateClassTS([
12391
12705
  TracedAs5("cli.event-handler.formatEvent", { recordParams: true, recordResult: false }),
12392
12706
  __legacyMetadataTS("design:type", Function),
package/dist/index.js CHANGED
@@ -9638,7 +9638,7 @@ class CliQuietModeService {
9638
9638
  // package.json
9639
9639
  var package_default = {
9640
9640
  name: "@ai-setting/roy-agent-cli",
9641
- version: "1.7.57",
9641
+ version: "1.7.58",
9642
9642
  type: "module",
9643
9643
  description: "CLI for roy-agent - Non-interactive command execution",
9644
9644
  main: "./dist/index.js",
@@ -11784,6 +11784,262 @@ import {
11784
11784
  } from "@ai-setting/roy-agent-core/env";
11785
11785
  import { TracedAs as TracedAs5 } from "@ai-setting/roy-agent-core";
11786
11786
  import { notifyReactFailure as notifyReactFailure2 } from "@ai-setting/roy-agent-core/env/error-reply";
11787
+
11788
+ // src/commands/shared/telemetry-notice.ts
11789
+ import { spawn } from "node:child_process";
11790
+ import { normalizeLarkAtMentions } from "@ai-setting/roy-agent-core/env/lark-mention-utils";
11791
+ import { deriveIdempotencyKey } from "@ai-setting/roy-agent-core/env/event-source/idempotency-key";
11792
+ var TELEMETRY_EVENT_TYPES = new Set([
11793
+ "task.background.started",
11794
+ "task.background.progress"
11795
+ ]);
11796
+ function isTelemetryEvent(type) {
11797
+ if (!type)
11798
+ return false;
11799
+ return TELEMETRY_EVENT_TYPES.has(type);
11800
+ }
11801
+ var MAX_TITLE_CHARS = 40;
11802
+ function pad2(n) {
11803
+ return n < 10 ? `0${n}` : String(n);
11804
+ }
11805
+ function formatElapsed(ms) {
11806
+ if (!Number.isFinite(ms) || ms <= 0)
11807
+ return "0m";
11808
+ const totalMin = Math.floor(ms / 60000);
11809
+ if (totalMin < 60)
11810
+ return `${totalMin}m`;
11811
+ const totalHour = Math.floor(totalMin / 60);
11812
+ const min = totalMin % 60;
11813
+ if (totalHour < 24) {
11814
+ return min === 0 ? `${totalHour}h` : `${totalHour}h${min}m`;
11815
+ }
11816
+ const day = Math.floor(totalHour / 24);
11817
+ const hour = totalHour % 24;
11818
+ return hour === 0 ? `${day}d` : `${day}d${hour}h`;
11819
+ }
11820
+ function formatClock(ts) {
11821
+ const d = new Date(ts);
11822
+ return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
11823
+ }
11824
+ function formatTimeoutLabel(ms) {
11825
+ if (!Number.isFinite(ms) || ms <= 0)
11826
+ return "0m";
11827
+ return formatElapsed(ms);
11828
+ }
11829
+ function truncateTitle(raw) {
11830
+ const s = raw.trim();
11831
+ if (s.length <= MAX_TITLE_CHARS)
11832
+ return s;
11833
+ return `${s.slice(0, MAX_TITLE_CHARS)}…`;
11834
+ }
11835
+ function pickString(v) {
11836
+ return typeof v === "string" && v.length > 0 ? v : undefined;
11837
+ }
11838
+ function pickNumber(v) {
11839
+ return typeof v === "number" && Number.isFinite(v) ? v : undefined;
11840
+ }
11841
+ function buildTelemetryNoticeText(type, payload, now = Date.now()) {
11842
+ const p = payload ?? {};
11843
+ const taskId = pickNumber(p.associatedTaskId);
11844
+ const bgId = pickString(p.backgroundTaskId);
11845
+ const desc = truncateTitle(pickString(p.description) ?? "后台任务");
11846
+ let label;
11847
+ if (taskId !== undefined)
11848
+ label = `Task #${taskId}`;
11849
+ else if (bgId)
11850
+ label = bgId;
11851
+ else
11852
+ label = "后台任务";
11853
+ const head = type === "task.background.started" ? `\uD83D\uDD14 ${label}「${desc}」已启动` : `\uD83D\uDD04 ${label}「${desc}」后台运行中`;
11854
+ const parts = [];
11855
+ const startedAt = pickNumber(p.startedAt);
11856
+ const timeoutMs = pickNumber(p.timeoutMs);
11857
+ if (type !== "task.background.started" && startedAt !== undefined) {
11858
+ parts.push(`已跑 ${formatElapsed(now - startedAt)}`);
11859
+ }
11860
+ if (startedAt !== undefined && timeoutMs !== undefined) {
11861
+ parts.push(`预计 ${formatClock(startedAt + timeoutMs)} 超时(timeout ${formatTimeoutLabel(timeoutMs)})`);
11862
+ }
11863
+ let text = head;
11864
+ if (parts.length > 0)
11865
+ text += `
11866
+ ${parts.join(",")}`;
11867
+ const progressMessage = pickString(p.progressMessage);
11868
+ if (progressMessage)
11869
+ text += `
11870
+ ${progressMessage}`;
11871
+ return text;
11872
+ }
11873
+ var DEFAULT_SEND_TIMEOUT_MS = 5000;
11874
+ var MAX_IDEMPOTENCY_KEY_LEN = 50;
11875
+ function deriveTelemetryIdempotencyKey(eventId, chatId) {
11876
+ const anchor = pickString(eventId) ?? `ts-${Date.now()}-${Math.random()}`;
11877
+ return deriveIdempotencyKey(["telemetry", anchor, chatId]);
11878
+ }
11879
+ function buildLarkSendArgs(input) {
11880
+ const { chatId, text, profile, idempotencyKey } = input;
11881
+ const args = [
11882
+ "im",
11883
+ "+messages-send",
11884
+ "--chat-id",
11885
+ chatId,
11886
+ "--markdown",
11887
+ normalizeLarkAtMentions(text),
11888
+ "--as",
11889
+ "bot"
11890
+ ];
11891
+ if (idempotencyKey) {
11892
+ args.push("--idempotency-key", idempotencyKey.slice(0, MAX_IDEMPOTENCY_KEY_LEN));
11893
+ }
11894
+ if (profile)
11895
+ args.unshift("--profile", profile);
11896
+ return args;
11897
+ }
11898
+ function sendLarkChatMarkdown(options) {
11899
+ const { chatId, text, profile, idempotencyKey } = options;
11900
+ const timeoutMs = options.timeoutMs ?? DEFAULT_SEND_TIMEOUT_MS;
11901
+ if (!chatId) {
11902
+ return Promise.reject(new Error("sendLarkChatMarkdown: empty chatId"));
11903
+ }
11904
+ return new Promise((resolve, reject) => {
11905
+ let settled = false;
11906
+ let timer;
11907
+ const finish = (err) => {
11908
+ if (settled)
11909
+ return;
11910
+ settled = true;
11911
+ if (timer)
11912
+ clearTimeout(timer);
11913
+ if (err)
11914
+ reject(err);
11915
+ else
11916
+ resolve();
11917
+ };
11918
+ const args = buildLarkSendArgs({ chatId, text, profile, idempotencyKey });
11919
+ let proc;
11920
+ try {
11921
+ proc = spawn("lark-cli", args, {
11922
+ shell: false,
11923
+ stdio: ["ignore", "pipe", "pipe"]
11924
+ });
11925
+ } catch (err) {
11926
+ finish(err instanceof Error ? err : new Error(String(err)));
11927
+ return;
11928
+ }
11929
+ let stderr = "";
11930
+ proc.stderr?.on("data", (chunk) => {
11931
+ stderr += chunk.toString();
11932
+ });
11933
+ timer = setTimeout(() => {
11934
+ try {
11935
+ proc.kill("SIGKILL");
11936
+ } catch {}
11937
+ finish(new Error(`lark-cli timed out after ${timeoutMs}ms`));
11938
+ }, timeoutMs);
11939
+ timer.unref?.();
11940
+ proc.on("close", (code) => {
11941
+ if (code === 0)
11942
+ finish();
11943
+ else
11944
+ finish(new Error(`lark-cli exited with code ${code}: ${stderr}`));
11945
+ });
11946
+ proc.on("error", (err) => finish(err));
11947
+ });
11948
+ }
11949
+ function targetFromEventMetadata(metadata) {
11950
+ if (!metadata)
11951
+ return;
11952
+ const replyChannel = metadata.replyChannel;
11953
+ const chatId = pickString(replyChannel?.chatId) ?? pickString(metadata.chatId);
11954
+ if (!chatId)
11955
+ return;
11956
+ const profile = pickString(replyChannel?.profile) ?? pickString(metadata.profile);
11957
+ return profile ? { chatId, profile } : { chatId };
11958
+ }
11959
+ async function targetFromTriggerEvent(eventStore, triggerEventId) {
11960
+ if (!eventStore || !triggerEventId)
11961
+ return;
11962
+ let persisted;
11963
+ try {
11964
+ persisted = await eventStore.getEventById(triggerEventId);
11965
+ } catch {
11966
+ return;
11967
+ }
11968
+ if (!persisted || typeof persisted !== "object")
11969
+ return;
11970
+ const metadata = persisted.metadata;
11971
+ if (!metadata || typeof metadata !== "object")
11972
+ return;
11973
+ return targetFromEventMetadata(metadata);
11974
+ }
11975
+
11976
+ // src/commands/shared/self-echo-filter.ts
11977
+ var BOT_SENDER_TYPES = new Set(["bot", "app"]);
11978
+ function pickString2(v) {
11979
+ return typeof v === "string" && v.length > 0 ? v : undefined;
11980
+ }
11981
+ function extractSenderIdentity(event) {
11982
+ if (!event)
11983
+ return {};
11984
+ const metadata = event.metadata;
11985
+ const payload = event.payload;
11986
+ const payloadMeta = payload?.metadata;
11987
+ const rawEvent = payload?.rawEvent;
11988
+ const innerEvent = rawEvent?.event;
11989
+ const innerSender = innerEvent?.sender;
11990
+ const topSender = rawEvent?.sender;
11991
+ const senderType = pickString2(metadata?.senderType) ?? pickString2(payloadMeta?.senderType) ?? pickString2(rawEvent?.sender_type) ?? pickString2(innerSender?.sender_type) ?? pickString2(topSender?.sender_type) ?? pickString2(payload?.senderType);
11992
+ const nestedSenderId = innerSender?.sender_id ?? topSender?.sender_id;
11993
+ const senderId = pickString2(metadata?.senderId) ?? pickString2(payloadMeta?.senderId) ?? pickString2(payload?.senderId) ?? pickString2(rawEvent?.sender_id) ?? (typeof nestedSenderId === "string" ? pickString2(nestedSenderId) : pickString2(nestedSenderId?.open_id) ?? pickString2(nestedSenderId?.user_id) ?? pickString2(nestedSenderId?.union_id));
11994
+ return {
11995
+ ...senderType ? { senderType } : {},
11996
+ ...senderId ? { senderId } : {}
11997
+ };
11998
+ }
11999
+ function isSelfEchoEvent(event, options = {}) {
12000
+ const { senderType, senderId } = extractSenderIdentity(event);
12001
+ if (options.enabled === false) {
12002
+ return {
12003
+ skip: false,
12004
+ reason: "disabled",
12005
+ ...senderType ? { senderType } : {},
12006
+ ...senderId ? { senderId } : {}
12007
+ };
12008
+ }
12009
+ if (senderType && BOT_SENDER_TYPES.has(senderType.toLowerCase())) {
12010
+ return {
12011
+ skip: true,
12012
+ reason: "bot-sender",
12013
+ ...senderType ? { senderType } : {},
12014
+ ...senderId ? { senderId } : {}
12015
+ };
12016
+ }
12017
+ if (senderId && options.selfBotIds && options.selfBotIds.length > 0) {
12018
+ if (options.selfBotIds.includes(senderId)) {
12019
+ return {
12020
+ skip: true,
12021
+ reason: "self-bot-id",
12022
+ ...senderType ? { senderType } : {},
12023
+ senderId
12024
+ };
12025
+ }
12026
+ }
12027
+ return {
12028
+ skip: false,
12029
+ ...senderType ? { senderType } : {},
12030
+ ...senderId ? { senderId } : {}
12031
+ };
12032
+ }
12033
+ var SELF_ECHO_FILTER_ENABLED_ENV = "ROY_AGENT_SELF_ECHO_FILTER";
12034
+ function readSelfEchoFilterEnabled(env = process.env) {
12035
+ const raw = env[SELF_ECHO_FILTER_ENABLED_ENV];
12036
+ if (raw === undefined)
12037
+ return true;
12038
+ const v = raw.trim().toLowerCase();
12039
+ return !(v === "0" || v === "false" || v === "off" || v === "no");
12040
+ }
12041
+
12042
+ // src/commands/shared/event-handler.ts
11787
12043
  function parseLarkPerTaskChatId(contextJson) {
11788
12044
  if (!contextJson)
11789
12045
  return;
@@ -11836,6 +12092,15 @@ class EventHandler {
11836
12092
  resolvedSessionByTrigger = new Map;
11837
12093
  eventStore = null;
11838
12094
  getHandlerForEsType = null;
12095
+ telemetrySender = (notice) => sendLarkChatMarkdown(notice);
12096
+ selfEchoFilter = {
12097
+ enabled: true,
12098
+ selfBotIds: []
12099
+ };
12100
+ selfEchoDroppedCount = 0;
12101
+ getSelfEchoDroppedCount() {
12102
+ return this.selfEchoDroppedCount;
12103
+ }
11839
12104
  constructor(options) {
11840
12105
  this.env = options.env;
11841
12106
  this.eventTypes = options.eventTypes;
@@ -11850,6 +12115,11 @@ class EventHandler {
11850
12115
  this.sessionComponent = options.sessionComponent ?? null;
11851
12116
  this.eventStore = options.eventStore ?? null;
11852
12117
  this.getHandlerForEsType = options.getHandlerForEsType ?? null;
12118
+ this.telemetrySender = options.telemetrySender ?? ((notice) => sendLarkChatMarkdown(notice));
12119
+ this.selfEchoFilter = {
12120
+ enabled: options.selfEchoFilter?.enabled ?? readSelfEchoFilterEnabled(),
12121
+ selfBotIds: options.selfEchoFilter?.selfBotIds ?? []
12122
+ };
11853
12123
  }
11854
12124
  start() {
11855
12125
  this.isStopped = false;
@@ -11896,9 +12166,45 @@ class EventHandler {
11896
12166
  if (this.isStopped) {
11897
12167
  return;
11898
12168
  }
12169
+ if (this.selfEchoFilter.enabled) {
12170
+ const verdict = isSelfEchoEvent(event, {
12171
+ selfBotIds: this.selfEchoFilter.selfBotIds
12172
+ });
12173
+ if (verdict.skip) {
12174
+ this.selfEchoDroppedCount += 1;
12175
+ console.error(`[EventHandler] self-echo event dropped (reason=${verdict.reason}` + `, senderType=${verdict.senderType ?? "-"}` + `, senderId=${verdict.senderId ?? "-"}` + `, type=${event.type})`);
12176
+ return;
12177
+ }
12178
+ }
12179
+ if (isTelemetryEvent(event.type)) {
12180
+ this.sendTelemetryNotice(event);
12181
+ return;
12182
+ }
11899
12183
  const formatted = await this.formatEvent(event);
11900
12184
  this.enqueue(formatted);
11901
12185
  }
12186
+ async sendTelemetryNotice(event) {
12187
+ try {
12188
+ const metadata = event.metadata;
12189
+ let target = targetFromEventMetadata(metadata);
12190
+ if (!target) {
12191
+ const triggerEventId = typeof metadata?.triggerEventId === "string" ? metadata.triggerEventId : undefined;
12192
+ target = await targetFromTriggerEvent(this.eventStore, triggerEventId);
12193
+ }
12194
+ if (!target) {
12195
+ return;
12196
+ }
12197
+ const text = buildTelemetryNoticeText(event.type, event.payload);
12198
+ await this.telemetrySender({
12199
+ chatId: target.chatId,
12200
+ ...target.profile ? { profile: target.profile } : {},
12201
+ text,
12202
+ idempotencyKey: deriveTelemetryIdempotencyKey(event.id, target.chatId)
12203
+ });
12204
+ } catch (err) {
12205
+ console.error(`[EventHandler] telemetry notice failed (swallowed) type=${event.type}`, err);
12206
+ }
12207
+ }
11902
12208
  async formatEvent(event) {
11903
12209
  if (NOTIFY_V2_TERMINAL_TYPES.has(event.type) && event.metadata?.triggerEventId && this.eventStore && this.getHandlerForEsType) {
11904
12210
  try {
@@ -12386,6 +12692,14 @@ __legacyDecorateClassTS([
12386
12692
  ]),
12387
12693
  __legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
12388
12694
  ], EventHandler.prototype, "handleEvent", null);
12695
+ __legacyDecorateClassTS([
12696
+ TracedAs5("event_handler.telemetry_notice", { recordParams: true, recordResult: false }),
12697
+ __legacyMetadataTS("design:type", Function),
12698
+ __legacyMetadataTS("design:paramtypes", [
12699
+ typeof EnvEvent === "undefined" ? Object : EnvEvent
12700
+ ]),
12701
+ __legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
12702
+ ], EventHandler.prototype, "sendTelemetryNotice", null);
12389
12703
  __legacyDecorateClassTS([
12390
12704
  TracedAs5("cli.event-handler.formatEvent", { recordParams: true, recordResult: false }),
12391
12705
  __legacyMetadataTS("design:type", Function),
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/roy-agent-linux-x64/bin/roy-agent
3
- size: 109885568 bytes
4
- sha256: 3a69d105f14b338232ff69fa3b1210da25fb37594d63457ccbf30f7fe3d973a5
3
+ size: 109897856 bytes
4
+ sha256: 0343f071cd373c6540cd1f19d6a561e9cacce93efac9be7b3f424490521d7044
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-setting/roy-agent-cli",
3
- "version": "1.7.57",
3
+ "version": "1.7.58",
4
4
  "type": "module",
5
5
  "description": "CLI for roy-agent - Non-interactive command execution",
6
6
  "main": "./dist/index.js",