feique 1.1.5 → 1.3.0

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 (44) hide show
  1. package/README.en.md +5 -1
  2. package/README.md +5 -1
  3. package/dist/backend/codex.d.ts +1 -0
  4. package/dist/backend/codex.js +1 -0
  5. package/dist/backend/codex.js.map +1 -1
  6. package/dist/bridge/commands.d.ts +4 -0
  7. package/dist/bridge/commands.js +44 -3
  8. package/dist/bridge/commands.js.map +1 -1
  9. package/dist/bridge/intent-classifier.d.ts +42 -0
  10. package/dist/bridge/intent-classifier.js +242 -0
  11. package/dist/bridge/intent-classifier.js.map +1 -0
  12. package/dist/bridge/service.d.ts +5 -0
  13. package/dist/bridge/service.js +224 -23
  14. package/dist/bridge/service.js.map +1 -1
  15. package/dist/codex/runner.d.ts +1 -0
  16. package/dist/codex/runner.js +3 -0
  17. package/dist/codex/runner.js.map +1 -1
  18. package/dist/collaboration/awareness.js +3 -2
  19. package/dist/collaboration/awareness.js.map +1 -1
  20. package/dist/collaboration/knowledge-gaps.d.ts +25 -0
  21. package/dist/collaboration/knowledge-gaps.js +158 -0
  22. package/dist/collaboration/knowledge-gaps.js.map +1 -0
  23. package/dist/collaboration/knowledge.js +20 -4
  24. package/dist/collaboration/knowledge.js.map +1 -1
  25. package/dist/collaboration/proactive-alerts.d.ts +39 -0
  26. package/dist/collaboration/proactive-alerts.js +127 -0
  27. package/dist/collaboration/proactive-alerts.js.map +1 -0
  28. package/dist/config/schema.d.ts +32 -0
  29. package/dist/config/schema.js +17 -0
  30. package/dist/config/schema.js.map +1 -1
  31. package/dist/feishu/client.d.ts +5 -0
  32. package/dist/feishu/client.js +44 -0
  33. package/dist/feishu/client.js.map +1 -1
  34. package/dist/feishu/text.d.ts +4 -0
  35. package/dist/feishu/text.js +17 -0
  36. package/dist/feishu/text.js.map +1 -1
  37. package/dist/memory/summarize.js +38 -6
  38. package/dist/memory/summarize.js.map +1 -1
  39. package/dist/observability/metrics.d.ts +1 -1
  40. package/dist/observability/metrics.js.map +1 -1
  41. package/dist/state/run-state-store.d.ts +1 -0
  42. package/dist/state/run-state-store.js +10 -5
  43. package/dist/state/run-state-store.js.map +1 -1
  44. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent-classifier.js","sourceRoot":"","sources":["../../src/bridge/intent-classifier.ts"],"names":[],"mappings":"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"}
@@ -39,6 +39,9 @@ export declare class FeiqueService {
39
39
  private readonly chatRateWindows;
40
40
  private maintenanceTimer?;
41
41
  private digestTimer?;
42
+ private readonly intentClassifier?;
43
+ /** Tracks the current incoming message for @mention in replies. */
44
+ private currentMessageContext?;
42
45
  constructor(config: BridgeConfig, feishuClient: FeishuClient, sessionStore: SessionStore, auditLog: AuditLog, logger: Logger, metrics?: MetricsRegistry | undefined, idempotencyStore?: IdempotencyStore, runStateStore?: RunStateStore, memoryStore?: MemoryStore, codexSessionIndex?: CodexSessionIndex, runtimeControl?: RuntimeControl | undefined, adminAuditLog?: AuditLog, configHistoryStore?: ConfigHistoryStore, handoffStore?: HandoffStore, trustStore?: TrustStore);
43
46
  recoverRuntimeState(): Promise<RunState[]>;
44
47
  startMaintenanceLoop(): void;
@@ -74,6 +77,8 @@ export declare class FeiqueService {
74
77
  private handleInsightsCommand;
75
78
  private handleTrustCommand;
76
79
  private handleDigestCommand;
80
+ private checkAndSendAlerts;
81
+ private handleGapsCommand;
77
82
  private handleTimelineCommand;
78
83
  private handleAdminCommand;
79
84
  private handleSessionAdoptCommand;
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { buildHelpText, isReadOnlyCommand, normalizeIncomingText, parseBridgeCommand, } from './commands.js';
4
+ import { buildHelpText, buildFullHelpText, isReadOnlyCommand, normalizeIncomingText, parseBridgeCommand, } from './commands.js';
5
5
  import { buildConversationKey } from '../state/session-store.js';
6
6
  import { buildMessageCard, buildStatusCard } from '../feishu/cards.js';
7
7
  import { TaskQueue } from './task-queue.js';
@@ -37,7 +37,10 @@ import { classifyOperation, enforceTrustBoundary, recordRunOutcome, formatTrustS
37
37
  import { buildProjectTimeline, buildOnboardingContext, formatTimeline, isNewActor } from '../collaboration/timeline.js';
38
38
  import { HandoffStore } from '../state/handoff-store.js';
39
39
  import { TrustStore } from '../state/trust-store.js';
40
+ import { IntentClassifier } from './intent-classifier.js';
40
41
  import { buildTeamDigest, formatTeamDigest, createDigestPeriod } from '../collaboration/digest.js';
42
+ import { checkRunAlerts, checkLongRunningAlerts, formatAlert, DEFAULT_ALERT_RULES } from '../collaboration/proactive-alerts.js';
43
+ import { detectKnowledgeGaps, formatKnowledgeGaps } from '../collaboration/knowledge-gaps.js';
41
44
  import { estimateCost } from '../observability/cost.js';
42
45
  export class FeiqueService {
43
46
  config;
@@ -62,6 +65,9 @@ export class FeiqueService {
62
65
  chatRateWindows = new Map();
63
66
  maintenanceTimer;
64
67
  digestTimer;
68
+ intentClassifier;
69
+ /** Tracks the current incoming message for @mention in replies. */
70
+ currentMessageContext;
65
71
  constructor(config, feishuClient, sessionStore, auditLog, logger, metrics, idempotencyStore = new IdempotencyStore(config.storage.dir), runStateStore = new RunStateStore(config.storage.dir), memoryStore = new MemoryStore(config.storage.dir), codexSessionIndex = new CodexSessionIndex(), runtimeControl, adminAuditLog = new AuditLog(config.storage.dir, 'admin-audit.jsonl'), configHistoryStore = new ConfigHistoryStore(config.storage.dir), handoffStore = new HandoffStore(config.storage.dir), trustStore = new TrustStore(config.storage.dir)) {
66
72
  this.config = config;
67
73
  this.feishuClient = feishuClient;
@@ -78,6 +84,20 @@ export class FeiqueService {
78
84
  this.configHistoryStore = configHistoryStore;
79
85
  this.handoffStore = handoffStore;
80
86
  this.trustStore = trustStore;
87
+ if (config.service.intent_classifier_enabled) {
88
+ const defaultBackend = config.backend?.default ?? 'codex';
89
+ const isClaude = defaultBackend === 'claude';
90
+ this.intentClassifier = new IntentClassifier({
91
+ enabled: true,
92
+ backend: defaultBackend,
93
+ backend_bin: isClaude ? (config.claude?.bin ?? 'claude') : config.codex.bin,
94
+ shell: isClaude ? config.claude?.shell : config.codex.shell,
95
+ pre_exec: isClaude ? config.claude?.pre_exec : config.codex.pre_exec,
96
+ ollama_base_url: config.embedding.provider === 'ollama' ? config.embedding.ollama_base_url : undefined,
97
+ timeout_ms: config.service.intent_classifier_timeout_ms,
98
+ min_confidence: config.service.intent_classifier_min_confidence,
99
+ });
100
+ }
81
101
  }
82
102
  async recoverRuntimeState() {
83
103
  const recovered = await this.runStateStore.recoverOrphanedRuns();
@@ -234,8 +254,25 @@ export class FeiqueService {
234
254
  await this.runMemoryMaintenance();
235
255
  }
236
256
  await this.runAuditMaintenance();
257
+ // Proactive: check for long-running tasks
258
+ try {
259
+ const activeRuns = await this.runStateStore.listRuns();
260
+ const longAlerts = checkLongRunningAlerts(activeRuns);
261
+ for (const alert of longAlerts) {
262
+ const text = formatAlert(alert);
263
+ for (const chatId of this.config.security.admin_chat_ids) {
264
+ try {
265
+ await this.feishuClient.sendText(chatId, text);
266
+ }
267
+ catch { /* best-effort */ }
268
+ }
269
+ await this.notifyProjectChats(alert.project_alias, text);
270
+ }
271
+ }
272
+ catch { /* best-effort */ }
237
273
  }
238
274
  async handleIncomingMessage(context) {
275
+ this.currentMessageContext = context;
239
276
  if (!context.text.trim() && context.attachments.length === 0) {
240
277
  return;
241
278
  }
@@ -263,7 +300,18 @@ export class FeiqueService {
263
300
  }
264
301
  const normalizedText = normalizeIncomingText(context.text);
265
302
  const selectionKey = await this.getSelectionConversationKey(context);
266
- const command = parseBridgeCommand(context.text);
303
+ let command = parseBridgeCommand(context.text);
304
+ // AI intent fallback: when regex doesn't match, try AI classification
305
+ if (command.kind === 'prompt' && this.intentClassifier) {
306
+ try {
307
+ const aiCommand = await this.intentClassifier.classify(normalizedText);
308
+ if (aiCommand) {
309
+ command = aiCommand;
310
+ this.logger.info({ originalText: normalizedText, aiCommand: command.kind }, 'AI intent classifier matched');
311
+ }
312
+ }
313
+ catch { /* AI classification is best-effort */ }
314
+ }
267
315
  this.metrics?.recordIncomingMessage(context.chat_type, command.kind);
268
316
  await this.auditLog.append({
269
317
  type: 'message.received',
@@ -278,7 +326,7 @@ export class FeiqueService {
278
326
  try {
279
327
  switch (command.kind) {
280
328
  case 'help':
281
- await this.sendTextReply(context.chat_id, buildHelpText(), context.message_id, context.text);
329
+ await this.sendTextReply(context.chat_id, command.detail ? buildFullHelpText() : buildHelpText(), context.message_id, context.text);
282
330
  return;
283
331
  case 'projects':
284
332
  await this.sendTextReply(context.chat_id, await this.buildProjectsText(selectionKey, context.chat_id), context.message_id, context.text);
@@ -367,6 +415,9 @@ export class FeiqueService {
367
415
  this.metrics?.recordCollaborationEvent('digest');
368
416
  await this.handleDigestCommand(context);
369
417
  return;
418
+ case 'gaps':
419
+ await this.handleGapsCommand(context);
420
+ return;
370
421
  case 'prompt':
371
422
  await this.handlePromptMessage(context, selectionKey, command.prompt, context.text);
372
423
  return;
@@ -383,7 +434,7 @@ export class FeiqueService {
383
434
  error: message,
384
435
  message_id: context.message_id,
385
436
  });
386
- await this.sendTextReply(context.chat_id, `处理失败:\n${message}`, context.message_id, context.text);
437
+ await this.sendTextReply(context.chat_id, `处理失败:\n${friendlyErrorMessage(message)}`, context.message_id, context.text);
387
438
  }
388
439
  }
389
440
  async handleCardAction(context) {
@@ -588,6 +639,7 @@ export class FeiqueService {
588
639
  project_alias: input.projectAlias,
589
640
  chat_id: input.chatId,
590
641
  actor_id: input.actorId,
642
+ actor_name: input.incomingMessage.actor_name,
591
643
  session_id: currentSession?.thread_id,
592
644
  project_root: projectRoot,
593
645
  prompt_excerpt: truncateExcerpt(input.prompt),
@@ -637,7 +689,8 @@ export class FeiqueService {
637
689
  projectConfig: backend.name === 'codex'
638
690
  ? {
639
691
  profile: input.project.profile ?? this.config.codex.default_profile,
640
- sandbox: input.project.sandbox ?? this.config.codex.default_sandbox,
692
+ model: input.project.codex_model,
693
+ sandbox: input.project.codex_sandbox ?? input.project.sandbox ?? this.config.codex.default_sandbox,
641
694
  tempDir: this.resolveProjectTempDir(input.projectAlias, input.project),
642
695
  cacheDir: this.resolveProjectCacheDir(input.projectAlias, input.project),
643
696
  }
@@ -691,7 +744,24 @@ export class FeiqueService {
691
744
  durationMs: Date.now() - startedAt,
692
745
  }, 'Codex run completed without a displayable final message');
693
746
  }
694
- const cardSummary = truncateForFeishuCard(excerpt || `${backendLabel} 已完成,但没有返回可显示文本。`);
747
+ // Extract and send any [SEND_FILE:path] markers before text reply
748
+ const { cleanText: excerptWithoutFiles, filePaths } = extractFileMarkers(excerpt);
749
+ if (filePaths.length > 0) {
750
+ for (const filePath of filePaths) {
751
+ try {
752
+ await this.feishuClient.sendFile(input.chatId, filePath);
753
+ this.logger.info({ chatId: input.chatId, filePath }, 'Sent file to Feishu');
754
+ }
755
+ catch (err) {
756
+ const msg = err instanceof Error ? err.message : String(err);
757
+ this.logger.warn({ chatId: input.chatId, filePath, error: msg }, 'Failed to send file to Feishu');
758
+ // Notify user about the failure inline
759
+ excerptWithoutFiles === excerpt || await this.feishuClient.sendText(input.chatId, `⚠️ 文件发送失败: ${filePath}\n${msg}`);
760
+ }
761
+ }
762
+ }
763
+ const finalExcerpt = excerptWithoutFiles.trim() || excerpt;
764
+ const cardSummary = truncateForFeishuCard(finalExcerpt || `${backendLabel} 已完成,但没有返回可显示文本。`);
695
765
  await this.auditLog.append({
696
766
  type: 'codex.run.completed',
697
767
  run_id: runId,
@@ -787,6 +857,14 @@ export class FeiqueService {
787
857
  this.metrics?.recordTrustLevel(input.projectAlias, updated.current_level);
788
858
  }
789
859
  catch { /* trust tracking is best-effort */ }
860
+ // Proactive alerts: check if this run triggers any team alerts
861
+ try {
862
+ const completedRunState = await this.runStateStore.getRun(runId);
863
+ if (completedRunState) {
864
+ await this.checkAndSendAlerts(completedRunState);
865
+ }
866
+ }
867
+ catch { /* alerts are best-effort */ }
790
868
  // Direction 2: Auto-extract knowledge
791
869
  if (this.config.service.memory_enabled && excerpt.length >= 100) {
792
870
  try {
@@ -808,7 +886,7 @@ export class FeiqueService {
808
886
  input,
809
887
  runId,
810
888
  title: `${backendLabel} 已完成`,
811
- body: excerpt || `${backendLabel} 已完成,但没有返回可显示文本。`,
889
+ body: finalExcerpt || `${backendLabel} 已完成,但没有返回可显示文本。`,
812
890
  runStatus: 'success',
813
891
  runPhase: '已完成',
814
892
  cardSummary,
@@ -868,6 +946,14 @@ export class FeiqueService {
868
946
  catch { /* trust tracking is best-effort */ }
869
947
  // Notify project chats about the failure
870
948
  await this.notifyProjectChats(input.projectAlias, `❌ 运行失败 [${input.projectAlias}]\n${message.slice(0, 200)}`);
949
+ // Proactive alerts on failure
950
+ try {
951
+ const failedRunState = await this.runStateStore.getRun(runId);
952
+ if (failedRunState) {
953
+ await this.checkAndSendAlerts(failedRunState);
954
+ }
955
+ }
956
+ catch { /* alerts are best-effort */ }
871
957
  }
872
958
  if (cancelled) {
873
959
  this.logger.warn({
@@ -892,10 +978,10 @@ export class FeiqueService {
892
978
  input,
893
979
  runId,
894
980
  title: cancelled ? '运行已取消' : '执行失败',
895
- body: cancelled ? '当前运行已取消。' : ['执行失败。', '', message].join('\n'),
981
+ body: cancelled ? '当前运行已取消。' : ['执行失败。', '', friendlyErrorMessage(message)].join('\n'),
896
982
  runStatus: cancelled ? 'cancelled' : 'failure',
897
983
  runPhase: cancelled ? '已取消' : '失败',
898
- cardSummary: truncateForFeishuCard(cancelled ? '当前运行已取消。' : message),
984
+ cardSummary: truncateForFeishuCard(cancelled ? '当前运行已取消。' : friendlyErrorMessage(message)),
899
985
  });
900
986
  }
901
987
  finally {
@@ -957,7 +1043,7 @@ export class FeiqueService {
957
1043
  async handleReadOnlyFollowupCommand(context, selectionKey, command, followupPrompt) {
958
1044
  switch (command.kind) {
959
1045
  case 'help':
960
- await this.sendTextReply(context.chat_id, buildHelpText(), context.message_id, context.text);
1046
+ await this.sendTextReply(context.chat_id, command.detail ? buildFullHelpText() : buildHelpText(), context.message_id, context.text);
961
1047
  return;
962
1048
  case 'projects':
963
1049
  await this.sendTextReply(context.chat_id, await this.buildProjectsText(selectionKey, context.chat_id), context.message_id, context.text);
@@ -1101,6 +1187,7 @@ export class FeiqueService {
1101
1187
  const scheduled = await this.scheduleProjectExecution(projectContext, {
1102
1188
  chatId: context.chat_id,
1103
1189
  actorId: context.actor_id,
1190
+ actorName: context.actor_name,
1104
1191
  prompt,
1105
1192
  }, async (runId) => {
1106
1193
  await this.executePrompt({
@@ -1473,6 +1560,44 @@ export class FeiqueService {
1473
1560
  const text = formatTeamDigest(digest);
1474
1561
  await this.sendTextReply(context.chat_id, text, context.message_id, context.text);
1475
1562
  }
1563
+ // ── Proactive Alerts ──
1564
+ async checkAndSendAlerts(completedRun) {
1565
+ const recentRuns = await this.runStateStore.listRuns();
1566
+ const projectConfig = this.config.projects[completedRun.project_alias];
1567
+ const dailyQuota = projectConfig?.daily_token_quota;
1568
+ const alerts = checkRunAlerts(completedRun, recentRuns, DEFAULT_ALERT_RULES, dailyQuota);
1569
+ if (alerts.length === 0)
1570
+ return;
1571
+ for (const alert of alerts) {
1572
+ const text = formatAlert(alert);
1573
+ // Send to admin chat IDs
1574
+ for (const chatId of this.config.security.admin_chat_ids) {
1575
+ try {
1576
+ await this.feishuClient.sendText(chatId, text);
1577
+ }
1578
+ catch { /* best-effort */ }
1579
+ }
1580
+ // Send to project notification channels
1581
+ await this.notifyProjectChats(alert.project_alias, text);
1582
+ await this.auditLog.append({
1583
+ type: 'collaboration.alert.sent',
1584
+ alert_kind: alert.kind,
1585
+ severity: alert.severity,
1586
+ project_alias: alert.project_alias,
1587
+ actor_id: alert.actor_id,
1588
+ });
1589
+ }
1590
+ }
1591
+ // ── Knowledge Gap Detection ──
1592
+ async handleGapsCommand(context) {
1593
+ const runs = await this.runStateStore.listRuns();
1594
+ const memories = this.config.service.memory_enabled
1595
+ ? await this.memoryStore.listRecentMemories({ scope: 'project', project_alias: '' }, 200)
1596
+ : [];
1597
+ const gaps = detectKnowledgeGaps(runs, memories);
1598
+ const text = formatKnowledgeGaps(gaps);
1599
+ await this.sendTextReply(context.chat_id, text, context.message_id, context.text);
1600
+ }
1476
1601
  // ── Direction 6: Timeline ──
1477
1602
  async handleTimelineCommand(context, selectionKey, projectArg) {
1478
1603
  const projectContext = await this.resolveProjectContext(context, selectionKey);
@@ -1576,6 +1701,8 @@ export class FeiqueService {
1576
1701
  session_operator_chat_ids: [],
1577
1702
  run_operator_chat_ids: [],
1578
1703
  config_admin_chat_ids: [],
1704
+ mcp_servers: [],
1705
+ skills: [],
1579
1706
  run_priority: 100,
1580
1707
  chat_rate_limit_window_seconds: 60,
1581
1708
  chat_rate_limit_max_runs: 20,
@@ -2615,12 +2742,14 @@ export class FeiqueService {
2615
2742
  });
2616
2743
  }
2617
2744
  async buildBridgePrompt(projectAlias, project, incomingMessage, userPrompt, memoryContext) {
2745
+ // Persona: project-level overrides global
2746
+ const persona = project.persona ?? this.config.service.persona;
2618
2747
  const prefixParts = [
2619
- 'You are replying through Feique, a team AI collaboration hub connected via Feishu.',
2620
- 'Keep the final response concise and action-oriented.',
2621
- 'When files change, summarize key paths and verification.',
2622
- 'Do not expose session IDs, run IDs, chat IDs, conversation keys, secrets, raw logs, or absolute local filesystem paths to Feishu users unless they explicitly ask for them.',
2623
- 'Prefer project-relative paths over absolute paths when referencing files.',
2748
+ persona
2749
+ ? `Replying via Feique (飞鹊). Persona: ${persona}`
2750
+ : 'Replying via Feique (飞鹊), a team AI collaboration hub for Feishu.',
2751
+ // Bridge rules compact, one block
2752
+ 'Feishu rules: Your text is auto-forwarded — do NOT send text to Feishu directly (causes duplicates). Files/images: send directly via Feishu APIs or use [SEND_FILE:/path] marker in response. Use project-relative paths. Do not expose internal IDs or secrets.',
2624
2753
  this.config.codex.bridge_instructions,
2625
2754
  ].filter(Boolean);
2626
2755
  if (project.instructions_prefix) {
@@ -2634,6 +2763,13 @@ export class FeiqueService {
2634
2763
  this.logger.warn({ error, projectAlias }, 'Failed to read project instructions prefix');
2635
2764
  }
2636
2765
  }
2766
+ // Project-level tools and skills
2767
+ if (project.skills && project.skills.length > 0) {
2768
+ prefixParts.push(`Available skills for this project: ${project.skills.join(', ')}`);
2769
+ }
2770
+ if (project.mcp_servers && project.mcp_servers.length > 0) {
2771
+ prefixParts.push(`Project MCP servers: ${project.mcp_servers.map((s) => s.name).join(', ')}`);
2772
+ }
2637
2773
  return [
2638
2774
  ...prefixParts,
2639
2775
  '',
@@ -2830,6 +2966,7 @@ export class FeiqueService {
2830
2966
  project_alias: projectContext.projectAlias,
2831
2967
  chat_id: metadata.chatId,
2832
2968
  actor_id: metadata.actorId,
2969
+ actor_name: metadata.actorName,
2833
2970
  project_root: projectRoot,
2834
2971
  prompt_excerpt: truncateExcerpt(metadata.prompt),
2835
2972
  status: 'queued',
@@ -2874,9 +3011,20 @@ export class FeiqueService {
2874
3011
  const lines = [
2875
3012
  reason === 'project' ? `当前项目 ${projectAlias} 已有任务在处理,已进入排队。` : '当前仓库正在被其他会话操作,已进入排队。',
2876
3013
  frontCount > 0 ? `前方还有 ${frontCount} 个任务。` : null,
2877
- blockingRun ? `阻塞状态: ${blockingRun.status}` : null,
2878
- reason === 'project-root' && blockingRun?.project_alias && blockingRun.project_alias !== projectAlias ? `占用项目: ${blockingRun.project_alias}` : null,
2879
3014
  ];
3015
+ if (blockingRun) {
3016
+ const actorName = blockingRun.actor_name ?? blockingRun.actor_id ?? '其他成员';
3017
+ lines.push(`当前执行: ${actorName}`);
3018
+ const elapsedMs = Date.now() - new Date(blockingRun.started_at).getTime();
3019
+ const elapsedMin = Math.round(elapsedMs / 60_000);
3020
+ if (elapsedMin > 0) {
3021
+ lines.push(`已运行: ${elapsedMin} 分钟`);
3022
+ }
3023
+ if (reason === 'project-root' && blockingRun.project_alias && blockingRun.project_alias !== projectAlias) {
3024
+ lines.push(`占用项目: ${blockingRun.project_alias}`);
3025
+ }
3026
+ }
3027
+ lines.push(`排队时间: ${new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`);
2880
3028
  return lines.filter(Boolean).join('\n');
2881
3029
  }
2882
3030
  buildRunStatusSummary(lastResponseExcerpt, activeRun) {
@@ -3274,13 +3422,24 @@ export class FeiqueService {
3274
3422
  await this.sessionStore.dropProjectSession(conversationKey, projectAlias, session.thread_id);
3275
3423
  }
3276
3424
  }
3277
- async sendTextReply(chatId, body, replyToMessageId, originalText, presentation) {
3425
+ async sendTextReply(chatId, body, replyToMessageId, originalText, presentation, mentionActor) {
3426
+ // In group chats, prepend @mention to the reply so the requester gets notified
3427
+ const actor = mentionActor ?? this.currentMessageContext;
3428
+ let mentionPrefix = '';
3429
+ if (actor?.chat_type === 'group' && actor.actor_id) {
3430
+ const displayName = actor.actor_name || actor.actor_id;
3431
+ mentionPrefix = `<at user_id="${actor.actor_id}">${displayName}</at>\n`;
3432
+ }
3433
+ const bodyWithMention = mentionPrefix ? mentionPrefix + body : body;
3278
3434
  const title = this.buildReplyTitle(this.sanitizeUserVisibleReply(body));
3279
- const formattedBody = this.sanitizeUserVisibleReply(this.formatQuotedReply(body, originalText));
3435
+ // Card mode uses replyToMessageId for threading — @mention tags render as
3436
+ // literal text inside card JSON, so use the clean body for cards.
3437
+ const formattedBodyClean = this.sanitizeUserVisibleReply(this.formatQuotedReply(body, originalText));
3438
+ const formattedBodyWithMention = this.sanitizeUserVisibleReply(this.formatQuotedReply(bodyWithMention, originalText));
3280
3439
  if (this.config.service.reply_mode === 'card') {
3281
3440
  const card = buildMessageCard({
3282
3441
  title,
3283
- body: formattedBody,
3442
+ body: formattedBodyClean,
3284
3443
  status: presentation?.status,
3285
3444
  phase: presentation?.phase,
3286
3445
  projectAlias: presentation?.projectAlias,
@@ -3296,7 +3455,7 @@ export class FeiqueService {
3296
3455
  return response;
3297
3456
  }
3298
3457
  if (this.config.service.reply_mode === 'post') {
3299
- const post = buildFeishuPost(title, formattedBody);
3458
+ const post = buildFeishuPost(title, formattedBodyWithMention);
3300
3459
  if (this.config.service.reply_quote_user_message && replyToMessageId) {
3301
3460
  const response = await this.feishuClient.sendPost(chatId, post, { replyToMessageId });
3302
3461
  await this.auditLog.append({
@@ -3318,7 +3477,7 @@ export class FeiqueService {
3318
3477
  return response;
3319
3478
  }
3320
3479
  if (this.config.service.reply_quote_user_message && replyToMessageId) {
3321
- const response = await this.feishuClient.sendText(chatId, this.sanitizeUserVisibleReply(body), { replyToMessageId });
3480
+ const response = await this.feishuClient.sendText(chatId, this.sanitizeUserVisibleReply(bodyWithMention), { replyToMessageId });
3322
3481
  await this.auditLog.append({
3323
3482
  type: 'message.replied',
3324
3483
  chat_id: chatId,
@@ -3328,7 +3487,7 @@ export class FeiqueService {
3328
3487
  });
3329
3488
  return response;
3330
3489
  }
3331
- const response = await this.feishuClient.sendText(chatId, formattedBody);
3490
+ const response = await this.feishuClient.sendText(chatId, formattedBodyWithMention);
3332
3491
  await this.auditLog.append({
3333
3492
  type: 'message.replied',
3334
3493
  chat_id: chatId,
@@ -3661,9 +3820,51 @@ function buildCardDedupeKey(context, action) {
3661
3820
  }
3662
3821
  return ['card', context.tenant_key ?? 'tenant', context.chat_id ?? 'chat', context.actor_id ?? 'actor', context.open_message_id, action].join('::');
3663
3822
  }
3823
+ /**
3824
+ * Extract [SEND_FILE:/path/to/file] markers from AI response text.
3825
+ * Returns cleaned text (markers removed) and list of file paths.
3826
+ */
3827
+ function extractFileMarkers(text) {
3828
+ const FILE_MARKER_RE = /\[SEND_FILE:([^\]]+)\]/g;
3829
+ const filePaths = [];
3830
+ let match;
3831
+ while ((match = FILE_MARKER_RE.exec(text)) !== null) {
3832
+ const filePath = match[1]?.trim();
3833
+ if (filePath) {
3834
+ filePaths.push(filePath);
3835
+ }
3836
+ }
3837
+ const cleanText = text.replace(FILE_MARKER_RE, '').replace(/\n{3,}/g, '\n\n').trim();
3838
+ return { cleanText, filePaths };
3839
+ }
3664
3840
  function truncateExcerpt(text, limit = 160) {
3665
3841
  return text.length > limit ? `${text.slice(0, limit)}...` : text;
3666
3842
  }
3843
+ /** Map raw error strings to user-friendly Chinese messages. */
3844
+ function friendlyErrorMessage(error) {
3845
+ const lower = error.toLowerCase();
3846
+ if (lower.includes('econnrefused') || lower.includes('enotfound') || lower.includes('econnreset')) {
3847
+ return '网络连接失败,请检查网络或稍后重试';
3848
+ }
3849
+ if (lower.includes('enoent') || lower.includes('enotdir')) {
3850
+ return '文件路径不存在,请检查项目配置';
3851
+ }
3852
+ if (lower.includes('permission denied') || lower.includes('eacces')) {
3853
+ return '权限不足,请检查文件权限';
3854
+ }
3855
+ if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('etimedout')) {
3856
+ return '执行超时,请尝试拆分为更小的任务';
3857
+ }
3858
+ if (lower.includes('rate limit') || lower.includes('429') || lower.includes('too many requests')) {
3859
+ return 'API 频率限制,请稍后重试';
3860
+ }
3861
+ if (lower.includes('enomem') || lower.includes('out of memory')) {
3862
+ return '内存不足,请关闭其他程序或减少并发任务';
3863
+ }
3864
+ // Default: show a truncated version of the raw error
3865
+ const truncated = error.length > 100 ? error.slice(0, 100) + '...' : error;
3866
+ return `执行异常: ${truncated}。如需帮助请联系管理员。`;
3867
+ }
3667
3868
  function splitCommaSeparatedValues(value) {
3668
3869
  return value
3669
3870
  .split(',')