@ai-setting/roy-agent-core 1.6.5 → 1.6.7

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 (30) hide show
  1. package/dist/env/agent/index.js +3 -3
  2. package/dist/env/event-source/index.js +1 -1
  3. package/dist/env/index.js +12 -12
  4. package/dist/env/llm/index.js +2 -2
  5. package/dist/env/plugin/index.js +1 -1
  6. package/dist/env/task/index.js +2 -2
  7. package/dist/env/task/tools/index.js +1 -1
  8. package/dist/env/tool/built-in/index.js +1 -1
  9. package/dist/env/tool/index.js +2 -2
  10. package/dist/env/workflow/engine/index.js +3 -3
  11. package/dist/env/workflow/index.js +5 -5
  12. package/dist/env/workflow/nodes/index.js +6 -2
  13. package/dist/env/workflow/tools/index.js +1 -1
  14. package/dist/index.js +14 -14
  15. package/dist/shared/@ai-setting/{roy-agent-core-ba6243hr.js → roy-agent-core-0tfv18rf.js} +175 -1
  16. package/dist/shared/@ai-setting/{roy-agent-core-z7dxedn6.js → roy-agent-core-2r1rc0fe.js} +12 -2
  17. package/dist/shared/@ai-setting/{roy-agent-core-xxfkvqqh.js → roy-agent-core-34xt19yg.js} +8 -7
  18. package/dist/shared/@ai-setting/{roy-agent-core-fb3wyr3d.js → roy-agent-core-39rvx3fw.js} +41 -3
  19. package/dist/shared/@ai-setting/{roy-agent-core-y1m7c2v7.js → roy-agent-core-8jmkghjc.js} +1 -1
  20. package/dist/shared/@ai-setting/{roy-agent-core-65qs21kf.js → roy-agent-core-dcd1s7ct.js} +4 -1
  21. package/dist/shared/@ai-setting/{roy-agent-core-bcd3y74k.js → roy-agent-core-gdkq55yg.js} +3 -2
  22. package/dist/shared/@ai-setting/{roy-agent-core-r3209bcd.js → roy-agent-core-gw43m8yd.js} +2 -2
  23. package/dist/shared/@ai-setting/{roy-agent-core-hp1n198d.js → roy-agent-core-jzza6c2r.js} +166 -45
  24. package/dist/shared/@ai-setting/{roy-agent-core-5rjccr3c.js → roy-agent-core-ka4b12eg.js} +88 -47
  25. package/dist/shared/@ai-setting/{roy-agent-core-p8zgxdgy.js → roy-agent-core-kenhsgnf.js} +3 -3
  26. package/dist/shared/@ai-setting/{roy-agent-core-155408xq.js → roy-agent-core-r08qk37k.js} +1 -1
  27. package/dist/shared/@ai-setting/{roy-agent-core-ktbkp661.js → roy-agent-core-sxqy40cs.js} +3 -3
  28. package/dist/shared/@ai-setting/{roy-agent-core-vf72n6qn.js → roy-agent-core-w7mhnc4c.js} +22 -4
  29. package/package.json +1 -1
  30. /package/dist/shared/@ai-setting/{roy-agent-core-6kygfd9x.js → roy-agent-core-02n40zbs.js} +0 -0
@@ -7,19 +7,21 @@ import {
7
7
  import {
8
8
  AgentComponentAdapter,
9
9
  init_agent_component_adapter
10
- } from "./roy-agent-core-vf72n6qn.js";
10
+ } from "./roy-agent-core-w7mhnc4c.js";
11
11
  import {
12
12
  TemplateResolver,
13
13
  init_template_resolver
14
14
  } from "./roy-agent-core-q88vxmx5.js";
15
15
  import {
16
+ PythonCallNode,
16
17
  SkillNode,
17
18
  ToolNode,
18
19
  WorkflowNode,
20
+ init_python_call_node,
19
21
  init_skill_node,
20
22
  init_tool_node,
21
23
  init_workflow_node
22
- } from "./roy-agent-core-ba6243hr.js";
24
+ } from "./roy-agent-core-0tfv18rf.js";
23
25
  import {
24
26
  WorkflowError,
25
27
  createWorkflowEvent,
@@ -1025,6 +1027,7 @@ var init_agent_node = __esm(() => {
1025
1027
  const promptTemplate = config.prompt || "";
1026
1028
  resolvedPrompt = context.templateResolver?.resolveValue(promptTemplate) ?? promptTemplate;
1027
1029
  }
1030
+ resolvedPrompt = this.applyPromptInjection(resolvedPrompt, context.workflowInput?.prompt_injection);
1028
1031
  let resolvedAgentType = agentType;
1029
1032
  let inheritedModel;
1030
1033
  let inheritedAllowedTools;
@@ -1135,6 +1138,16 @@ var init_agent_node = __esm(() => {
1135
1138
  };
1136
1139
  }
1137
1140
  }
1141
+ applyPromptInjection(basePrompt, promptInjection) {
1142
+ if (typeof promptInjection !== "string" || promptInjection.length === 0) {
1143
+ return basePrompt;
1144
+ }
1145
+ return `${basePrompt}
1146
+
1147
+ ---
1148
+ [Caller Injection]
1149
+ ${promptInjection}`;
1150
+ }
1138
1151
  async resolveAgentSessionIdFromWorkflow(context) {
1139
1152
  const sessionComponent = context.sessionComponent;
1140
1153
  if (!sessionComponent) {
@@ -1197,6 +1210,9 @@ var init_agent_node = __esm(() => {
1197
1210
  __legacyDecorateClassTS([
1198
1211
  TracedAs("workflow.agent.execute")
1199
1212
  ], AgentNode.prototype, "execute", null);
1213
+ __legacyDecorateClassTS([
1214
+ TracedAs("workflow.agent.applyPromptInjection", { recordParams: true, recordResult: false })
1215
+ ], AgentNode.prototype, "applyPromptInjection", null);
1200
1216
  __legacyDecorateClassTS([
1201
1217
  TracedAs("agent.node.createAgentSubSession", { recordParams: true, recordResult: true, log: true })
1202
1218
  ], AgentNode.prototype, "createAgentSubSession", null);
@@ -1297,6 +1313,9 @@ class NodeRegistry {
1297
1313
  this.factories.set("ask_user", (definition) => {
1298
1314
  return new AskUserNode(definition);
1299
1315
  });
1316
+ this.factories.set("python_call", (definition) => {
1317
+ return new PythonCallNode(definition);
1318
+ });
1300
1319
  }
1301
1320
  register(type, factory) {
1302
1321
  this.factories.set(type, factory);
@@ -1323,6 +1342,7 @@ var init_node_registry = __esm(() => {
1323
1342
  init_agent_component_adapter();
1324
1343
  init_workflow_node();
1325
1344
  init_ask_user_node();
1345
+ init_python_call_node();
1326
1346
  });
1327
1347
 
1328
1348
  // src/env/workflow/utils/agent-session-metadata.ts
@@ -1395,7 +1415,16 @@ var init_engine = __esm(() => {
1395
1415
  const definition = "definition" in workflow ? workflow.definition : workflow;
1396
1416
  const workflowId = "id" in workflow ? workflow.id : `inline_${definition.name}`;
1397
1417
  const workflowName = definition.name;
1398
- const runId = this.generateRunId();
1418
+ let runId;
1419
+ if (options?.runId) {
1420
+ const reserved = options.runId;
1421
+ if (typeof reserved !== "string" || !reserved.startsWith("workflow_")) {
1422
+ throw new Error(`[WorkflowEngine.createSession] reserved runId must start with 'workflow_'; got ${JSON.stringify(reserved)}`);
1423
+ }
1424
+ runId = reserved;
1425
+ } else {
1426
+ runId = this.generateRunId();
1427
+ }
1399
1428
  const sessionId = this.getSessionId(runId);
1400
1429
  const metadata = {
1401
1430
  type: "workflow",
@@ -2169,6 +2198,9 @@ var init_engine = __esm(() => {
2169
2198
  let timer;
2170
2199
  const timeoutPromise = new Promise((_, reject) => {
2171
2200
  timer = setTimeout(() => {
2201
+ this.stop(sessionId, "execution timeout").catch((stopError) => {
2202
+ logger3.warn(`[WorkflowEngine.waitForCompletion] Failed to stop ${sessionId} after execution timeout: ${stopError instanceof Error ? stopError.message : String(stopError)}`);
2203
+ });
2172
2204
  reject(new Error(`Workflow execution timeout: ${timeoutMs}ms`));
2173
2205
  }, timeoutMs);
2174
2206
  });
@@ -2251,6 +2283,12 @@ var init_engine = __esm(() => {
2251
2283
  __legacyDecorateClassTS([
2252
2284
  TracedAs("workflow.engine.failWorkflow", { recordParams: true, recordResult: true })
2253
2285
  ], WorkflowEngine.prototype, "failWorkflow", null);
2286
+ __legacyDecorateClassTS([
2287
+ TracedAs("workflow.engine.waitForCompletion", {
2288
+ recordParams: true,
2289
+ recordResult: true
2290
+ })
2291
+ ], WorkflowEngine.prototype, "waitForCompletion", null);
2254
2292
  });
2255
2293
 
2256
2294
  // src/env/workflow/engine/index.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  AgentComponentAdapter,
3
3
  init_agent_component_adapter
4
- } from "./roy-agent-core-vf72n6qn.js";
4
+ } from "./roy-agent-core-w7mhnc4c.js";
5
5
  import"./roy-agent-core-1db4vpc6.js";
6
6
  import"./roy-agent-core-e25xkv53.js";
7
7
  import"./roy-agent-core-gp872e7m.js";
@@ -432,7 +432,10 @@ function processThinkingStream(textDelta, config, state) {
432
432
  async function invoke(config, options, ctx) {
433
433
  try {
434
434
  const emitToEnv = createEnvEventEmitter(options.env, options.context);
435
- logger.info("[invoke] Created event emitter, env available:", !!options.env, "context:", options.context);
435
+ logger.info("[invoke] Created event emitter", {
436
+ envAvailable: !!options.env,
437
+ hasContext: options.context !== undefined
438
+ });
436
439
  const { providerId, modelId } = parseModelString(options.model || config.model);
437
440
  const provider = createProviderInstance(providerId, config.apiKey, config.baseURL);
438
441
  if (!provider) {
@@ -24,7 +24,7 @@ import {
24
24
  searchTasksTool,
25
25
  treeTasksTool,
26
26
  updateTaskTool
27
- } from "./roy-agent-core-ktbkp661.js";
27
+ } from "./roy-agent-core-sxqy40cs.js";
28
28
  import {
29
29
  createOperationTool,
30
30
  deleteOperationTool,
@@ -148,7 +148,8 @@ class DefaultTagService {
148
148
  return this.store.searchTasks(query, limit);
149
149
  }
150
150
  async searchTasksByKeywords(keywords, options) {
151
- return this.store.searchTasksByKeywords(keywords, options);
151
+ const result = await this.store.searchTasksByKeywords(keywords, options);
152
+ return result.tasks;
152
153
  }
153
154
  async findSimilarTasksByKeywords(keywords, options) {
154
155
  return this.store.findSimilarTasksByKeywords(keywords, options);
@@ -73,7 +73,7 @@ var killTree = (proc, signal = "SIGKILL") => {
73
73
  var makeBashSpawner = () => {
74
74
  return {
75
75
  run: (command, options) => {
76
- return Effect.gen(function* () {
76
+ return Effect.scoped(Effect.gen(function* () {
77
77
  const cwd = options?.cwd;
78
78
  const timeout = options?.timeout ?? 60000;
79
79
  const env = options?.env ?? {};
@@ -175,7 +175,7 @@ var makeBashSpawner = () => {
175
175
  success: !timedOut && exitCode === 0,
176
176
  timedOut
177
177
  };
178
- });
178
+ }));
179
179
  }
180
180
  };
181
181
  };
@@ -194,15 +194,23 @@ var timerHandler = {
194
194
 
195
195
  // src/env/event-source/bounty-im-handler.ts
196
196
  function readBountyIMConfig(config) {
197
- const topLevelAddress = config.address;
198
197
  const topLevelUrl = config.imServerUrl;
199
- const optionsAddress = config.options?.address;
198
+ const topLevelEmail = config.email;
200
199
  const optionsUrl = config.options?.imServerUrl;
200
+ const optionsEmail = config.options?.email;
201
201
  return {
202
- address: topLevelAddress || optionsAddress,
202
+ email: topLevelEmail || optionsEmail,
203
203
  imServerUrl: topLevelUrl || optionsUrl
204
204
  };
205
205
  }
206
+ function assertNoLegacyAddressField(config) {
207
+ const legacyTopLevel = config.address;
208
+ const legacyInOptions = config.options?.address;
209
+ const legacyValue = typeof legacyTopLevel === "string" && legacyTopLevel.trim() !== "" && legacyTopLevel || typeof legacyInOptions === "string" && legacyInOptions.trim() !== "" && legacyInOptions;
210
+ if (legacyValue) {
211
+ throw new Error(`[BountyIM] v0.14 strict email-only: legacy 'address' field is no longer supported. ` + `Found: address="${legacyValue}". ` + `Migration: remove the 'address' field and add 'email' (your registered bounty email) ` + `to event-sources.json. Example: { "id": "<source-id>", "type": "bounty-im", ` + `"email": "<you@bounty.example.com>", "imServerUrl": "<ws-url>" }`);
212
+ }
213
+ }
206
214
  function readBountyIMTlsSkipVerify(config) {
207
215
  const topLevel = config.tlsSkipVerify;
208
216
  const fromOptions = config.options?.tlsSkipVerify;
@@ -213,26 +221,101 @@ function buildDefaultBountyIMSystemPrompt(input) {
213
221
  const server = input.imServerUrl || "ws://localhost:4005/ws";
214
222
  const httpScheme = server.startsWith("wss://") ? "https://" : "http://";
215
223
  const endpoint = server.replace(/^wss?:\/\//, httpScheme).replace(/\/ws$/, "");
224
+ const email = input.currentEmail || "<your-registered-email>";
225
+ const command = `bounty com send --server-url ${endpoint} --from-email ${email} --to-email <recipient-email> --body "..."`;
226
+ const tlsCommand = ` bounty com send --server-url ${endpoint} --from-email ${email} --to-email <recipient-email> --body "..."`;
227
+ const rule = `规则:from-email 永远固定为 ${email}; to-email 是收到消息的 from 字段。`;
216
228
  return [
217
229
  "你正在通过 Bounty IM 与其他 agent 通信。",
218
230
  "",
219
- `你的 IM 地址:${input.currentAddress}`,
231
+ `你的 IM 身份(v0.14 strict email-only):${email}`,
220
232
  `IM Server:${endpoint} (k8s remote server)`,
221
233
  "",
222
- "回复收到的消息时,**必须**使用以下命令格式(注意 --endpoint 不可省略,省略会默认连 localhost:4000 而失败):",
234
+ "回复收到的消息时,**必须**使用以下命令格式(注意 --server-url 不可省略,省略会默认连 localhost:4000 而失败):",
223
235
  "",
224
- ` bounty com send --endpoint ${endpoint} -f ${input.currentAddress} -t <to-address> -b "<body>"`,
236
+ ` ${command}`,
225
237
  "",
226
238
  "如果遇到 TLS 错误(自签名证书),加 -k:",
227
- ` bounty com send --endpoint ${endpoint} -k -f ${input.currentAddress} -t <to-address> -b "<body>"`,
239
+ tlsCommand,
228
240
  "",
229
- `规则:to-address 总是收到的消息的 from 字段;from-address 永远固定为 ${input.currentAddress}。`
241
+ rule
230
242
  ].join(`
231
243
  `);
232
244
  }
245
+ function translateImServerUrlToHttpApi(imServerUrl) {
246
+ const httpScheme = imServerUrl.startsWith("wss://") ? "https://" : "http://";
247
+ return imServerUrl.replace(/^wss?:\/\//, httpScheme).replace(/\/ws$/, "");
248
+ }
249
+ function resolveCanonicalToEmail(canonical, profileEmail) {
250
+ if (!canonical)
251
+ return null;
252
+ const trimmed = canonical.trim();
253
+ if (!trimmed)
254
+ return null;
255
+ const UUID_HOST = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}@(.+)$/i;
256
+ const match = trimmed.match(UUID_HOST);
257
+ if (match) {
258
+ const host = match[1];
259
+ if (host === "authenticated") {
260
+ return profileEmail?.trim() || null;
261
+ }
262
+ return null;
263
+ }
264
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
265
+ return trimmed;
266
+ }
267
+ return null;
268
+ }
233
269
  function resolveBountyIMSystemPrompt(input) {
234
270
  return input.configSystemPrompt || buildDefaultBountyIMSystemPrompt(input);
235
271
  }
272
+ function isSelfEcho(msg, currentEmail) {
273
+ if (!msg || typeof msg !== "object")
274
+ return false;
275
+ const m = msg;
276
+ const fromEmailRaw = typeof m.fromEmail === "string" ? m.fromEmail.trim() : "";
277
+ const currentTrim = typeof currentEmail === "string" ? currentEmail.trim() : "";
278
+ if (fromEmailRaw && currentTrim) {
279
+ return fromEmailRaw.toLowerCase() === currentTrim.toLowerCase();
280
+ }
281
+ const fromAddr = typeof m.from === "string" ? m.from : undefined;
282
+ const toAddr = typeof m.to === "string" ? m.to : undefined;
283
+ if (fromAddr && toAddr) {
284
+ const fromLocal = fromAddr.split("@")[0]?.trim();
285
+ const toLocal = toAddr.split("@")[0]?.trim();
286
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
287
+ if (fromLocal && toLocal && UUID_RE.test(fromLocal) && UUID_RE.test(toLocal)) {
288
+ return fromLocal.toLowerCase() === toLocal.toLowerCase();
289
+ }
290
+ }
291
+ return false;
292
+ }
293
+ function isWrongAddress(msg, currentEmail) {
294
+ if (!msg || typeof msg !== "object")
295
+ return false;
296
+ const m = msg;
297
+ const toAddr = typeof m.to === "string" ? m.to.trim() : "";
298
+ if (!toAddr)
299
+ return false;
300
+ const currentTrim = typeof currentEmail === "string" ? currentEmail.trim() : "";
301
+ if (!currentTrim)
302
+ return false;
303
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
304
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
305
+ const toLocal = toAddr.split("@")[0] ?? "";
306
+ const currentLocal = currentTrim.split("@")[0] ?? "";
307
+ const toIsEmail = EMAIL_RE.test(toAddr) && !UUID_RE.test(toLocal);
308
+ const currentIsEmail = EMAIL_RE.test(currentTrim) && !UUID_RE.test(currentLocal);
309
+ const toIsUuid = UUID_RE.test(toLocal);
310
+ const currentIsUuid = UUID_RE.test(currentLocal);
311
+ if (toIsEmail && currentIsEmail) {
312
+ return toAddr.toLowerCase() !== currentTrim.toLowerCase();
313
+ }
314
+ if (toIsUuid && currentIsUuid) {
315
+ return toLocal.toLowerCase() !== currentLocal.toLowerCase();
316
+ }
317
+ return false;
318
+ }
236
319
  function shouldWriteBunTlsSkipEnv(tlsSkipVerify, isBunRuntimeOverride) {
237
320
  const isBun = isBunRuntimeOverride ?? typeof Bun !== "undefined";
238
321
  return tlsSkipVerify === true && isBun;
@@ -247,15 +330,19 @@ class BountyIMInstance {
247
330
  constructor(config) {
248
331
  this.config = config;
249
332
  }
333
+ getCurrentIdentity() {
334
+ const { email } = readBountyIMConfig(this.config);
335
+ return { email };
336
+ }
250
337
  getCurrentAddress() {
251
- const { address } = readBountyIMConfig(this.config);
252
- return address || "unknown";
338
+ const { email } = readBountyIMConfig(this.config);
339
+ return email || "unknown";
253
340
  }
254
341
  getCurrentSystemPrompt() {
255
- const imServerUrl = readBountyIMConfig(this.config).imServerUrl;
342
+ const { email, imServerUrl } = readBountyIMConfig(this.config);
256
343
  return resolveBountyIMSystemPrompt({
257
344
  configSystemPrompt: this.config.systemPrompt,
258
- currentAddress: this.getCurrentAddress(),
345
+ currentEmail: email,
259
346
  imServerUrl
260
347
  });
261
348
  }
@@ -266,13 +353,14 @@ class BountyIMInstance {
266
353
  if (this.status === "running")
267
354
  return;
268
355
  this.setStatus("starting");
269
- const { address, imServerUrl } = readBountyIMConfig(this.config);
356
+ assertNoLegacyAddressField(this.config);
357
+ const { email, imServerUrl } = readBountyIMConfig(this.config);
270
358
  if (!imServerUrl) {
271
359
  throw new Error("imServerUrl is required for bounty-im event source");
272
360
  }
273
361
  const wsUrl = new URL(imServerUrl);
274
- if (address) {
275
- wsUrl.searchParams.set("address", address);
362
+ if (email) {
363
+ wsUrl.searchParams.set("email", email);
276
364
  }
277
365
  console.log(`[BountyIM] Connecting to ${wsUrl.toString()}...`);
278
366
  try {
@@ -287,7 +375,7 @@ class BountyIMInstance {
287
375
  rejectUnauthorized: !tlsSkipVerify
288
376
  });
289
377
  this.ws.on("open", () => {
290
- console.log(`[BountyIM] Connected${address ? ` as ${address}` : ""}`);
378
+ console.log(`[BountyIM] Connected${email ? ` as ${email}` : ""}`);
291
379
  this.setStatus("running");
292
380
  });
293
381
  this.ws.on("message", (data) => {
@@ -325,6 +413,15 @@ class BountyIMInstance {
325
413
  setStatus(status) {
326
414
  this.status = status;
327
415
  }
416
+ sendAck(messageId) {
417
+ if (!messageId || !this.ws)
418
+ return;
419
+ try {
420
+ this.ws.send(JSON.stringify({ event: "ack", data: { messageIds: [messageId] } }));
421
+ } catch (ackErr) {
422
+ console.error(`[BountyIM] 发送 ACK 失败:`, ackErr);
423
+ }
424
+ }
328
425
  handleMessage(data) {
329
426
  this.buffer += data;
330
427
  try {
@@ -351,25 +448,44 @@ class BountyIMInstance {
351
448
  }
352
449
  const eventType = `bounty-im.${evt.event || "message"}`;
353
450
  const fromAddress = msg.from || "unknown";
354
- const currentAddress = this.getCurrentAddress();
355
- const toAddress = msg.to || currentAddress;
356
- if (msg.to && msg.to !== currentAddress) {
357
- console.warn(`[BountyIM] Warning: message.to (${msg.to}) !== current address (${currentAddress})`);
451
+ const currentEmail = this.getCurrentIdentity().email || "unknown";
452
+ const toAddress = msg.to || currentEmail;
453
+ if (isSelfEcho(msg, currentEmail)) {
454
+ console.log(`[BountyIM] Skipped self-echo: from=${msg.from ?? "?"} to=${msg.to ?? "?"}`);
455
+ this.sendAck(msg.id);
456
+ return;
457
+ }
458
+ if (isWrongAddress(msg, currentEmail)) {
459
+ console.warn(`[BountyIM] Skipped wrong-address event: to=${msg.to ?? "?"} currentEmail=${currentEmail}`);
460
+ this.sendAck(msg.id);
461
+ return;
462
+ }
463
+ if (msg.to && msg.to !== currentEmail) {
464
+ console.warn(`[BountyIM] Warning: message.to (${msg.to}) !== current email (${currentEmail})`);
358
465
  }
359
466
  const msgContent = msg.content;
360
467
  const content = msgContent?.type === "text" ? String(msgContent.body || "") : "";
361
- const { imServerUrl } = readBountyIMConfig(this.config);
468
+ const { imServerUrl, email } = readBountyIMConfig(this.config);
362
469
  const agent = this.config.handleRule?.agent;
470
+ const fromEmailRaw = msg.fromEmail;
471
+ const fromEmailResolved = resolveCanonicalToEmail(fromAddress, email);
472
+ const senderEmail = typeof fromEmailRaw === "string" && fromEmailRaw.trim() !== "" && fromEmailRaw.trim() || fromEmailResolved || fromAddress;
363
473
  const metadata = {
364
474
  eventType,
365
475
  senderId: fromAddress,
476
+ senderEmail,
366
477
  chatId: toAddress,
367
478
  agent
368
479
  };
480
+ const httpApiUrl = imServerUrl ? translateImServerUrlToHttpApi(imServerUrl) : "<your-server-url>";
481
+ const fromEmailForCmd = email || "<your-registered-email>";
482
+ const toEmailForCmd = senderEmail !== fromAddress ? senderEmail : "<recipient-registered-email>";
483
+ const replyCommandHint = `bounty com send --server-url ${httpApiUrl} --from-email ${fromEmailForCmd} --to-email ${toEmailForCmd} --body "..."`;
484
+ const inlineReplyHint = `bounty com send --server-url ${httpApiUrl} --from-email ${fromEmailForCmd} --to-email ${toEmailForCmd} --body "回复内容"`;
369
485
  let recommendedAction;
370
- if (evt.event === "message") {
486
+ if (evt.event === "message" && fromAddress !== toAddress) {
371
487
  recommendedAction = {
372
- action: `处理消息并通过 bounty com send 回复发件人。使用格式:bounty com send -f ${toAddress} -t ${fromAddress} -b "回复内容"`,
488
+ action: `处理消息并通过 bounty com send 回复发件人。使用格式:${replyCommandHint}`,
373
489
  replyTo: {
374
490
  chatId: fromAddress
375
491
  }
@@ -379,14 +495,15 @@ class BountyIMInstance {
379
495
  type: "bounty-im",
380
496
  chatId: fromAddress,
381
497
  params: {
382
- from: toAddress,
498
+ from: email || toAddress,
383
499
  to: fromAddress,
384
500
  imServerUrl
385
501
  }
386
502
  };
387
- const displayMessage = content ? `[From ${fromAddress}] ${content}
503
+ const displayFrom = senderEmail;
504
+ const displayMessage = content ? `[From ${displayFrom}] ${content}
388
505
 
389
- \uD83D\uDCA1 回复: bounty com send -f ${toAddress} -t ${fromAddress} -b "回复内容"` : this.formatMessage(rawEvent);
506
+ \uD83D\uDCA1 回复: ${inlineReplyHint}` : this.formatMessage(rawEvent);
390
507
  const event = {
391
508
  id: `es-${this.config.id}-${Date.now()}`,
392
509
  type: "event-source.event.bounty-im",
@@ -410,18 +527,10 @@ class BountyIMInstance {
410
527
  }
411
528
  };
412
529
  this.eventHandler?.(event);
413
- if (msg.id && this.ws) {
414
- try {
415
- const ackPayload = JSON.stringify({
416
- event: "ack",
417
- data: { messageIds: [msg.id] }
418
- });
419
- this.ws.send(ackPayload);
420
- console.log(`[BountyIM] 已发送 ACK for message: ${msg.id}`);
421
- } catch (ackErr) {
422
- console.error(`[BountyIM] 发送 ACK 失败:`, ackErr);
423
- }
530
+ if (msg.id) {
531
+ console.log(`[BountyIM] 已发送 ACK for message: ${msg.id}`);
424
532
  }
533
+ this.sendAck(msg.id);
425
534
  } catch (err) {
426
535
  console.error(`[BountyIM] Error processing message:`, err);
427
536
  }
@@ -451,11 +560,19 @@ var bountyIMHandler = {
451
560
  errors.push("EventSource ID is required");
452
561
  if (!config.name)
453
562
  errors.push("EventSource name is required");
454
- const { address, imServerUrl } = readBountyIMConfig(config);
455
- if (!address) {
456
- errors.push("Bounty IM address is required (top-level address or options.address)");
457
- } else if (!/^[\w-]+@[\w.-]+$/.test(address)) {
458
- errors.push("Bounty IM address format invalid (expected: agent-id@host)");
563
+ try {
564
+ assertNoLegacyAddressField(config);
565
+ } catch (err) {
566
+ errors.push(err instanceof Error ? err.message : String(err));
567
+ }
568
+ const { imServerUrl, email } = readBountyIMConfig(config);
569
+ if (!email) {
570
+ errors.push("Bounty IM email is required (v0.14 strict email-only; top-level email or options.email)");
571
+ }
572
+ if (email !== undefined && email !== "") {
573
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
574
+ errors.push("Bounty IM email format invalid (expected: user@domain.tld)");
575
+ }
459
576
  }
460
577
  if (!imServerUrl) {
461
578
  errors.push("Bounty IM imServerUrl is required (top-level imServerUrl or options.imServerUrl)");
@@ -540,7 +657,7 @@ function extractMetadata(rawEvent, eventType) {
540
657
  const profileFlag = profile2 ? `--profile ${profile2} ` : "";
541
658
  const profileHint = profile2 ? `(⚠️ 必须使用 profile="${profile2}" 调用 lark-cli,否则会因 openId 跨 app 失败)` : "";
542
659
  recommendedAction = {
543
- action: `处理飞书消息并以机器人身份回复${profileHint},例如:lark-cli ${profileFlag}im +messages-reply --message-id "xxxxid" --as bot --text "回复内容"`,
660
+ action: `处理飞书消息并以机器人身份回复${profileHint},例如:lark-cli ${profileFlag}im +messages-reply --message-id "xxxxid" --as bot --markdown "Markdown 格式回复内容"`,
544
661
  replyTo: {
545
662
  appId: metadata.appId,
546
663
  profile: profile2,
@@ -808,8 +925,9 @@ class LarkCliInstance {
808
925
  }
809
926
  const event = rawEvent;
810
927
  let eventType;
811
- if (event.header?.event_type) {
812
- eventType = event.header.event_type;
928
+ const eventHeader = event.header;
929
+ if (eventHeader?.event_type) {
930
+ eventType = eventHeader.event_type;
813
931
  } else if (event.schema) {
814
932
  eventType = `lark.${event.schema}`;
815
933
  } else if (event.type) {
@@ -1310,6 +1428,9 @@ class EventSourceComponent extends BaseComponent {
1310
1428
  return Array.from(this.sources.values());
1311
1429
  }
1312
1430
  getStatus(id) {
1431
+ if (id === undefined) {
1432
+ return this._status;
1433
+ }
1313
1434
  return this.statuses.get(id);
1314
1435
  }
1315
1436
  getEventSourceStatus(id) {