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.
- package/README.en.md +5 -1
- package/README.md +5 -1
- package/dist/backend/codex.d.ts +1 -0
- package/dist/backend/codex.js +1 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/bridge/commands.d.ts +4 -0
- package/dist/bridge/commands.js +44 -3
- package/dist/bridge/commands.js.map +1 -1
- package/dist/bridge/intent-classifier.d.ts +42 -0
- package/dist/bridge/intent-classifier.js +242 -0
- package/dist/bridge/intent-classifier.js.map +1 -0
- package/dist/bridge/service.d.ts +5 -0
- package/dist/bridge/service.js +224 -23
- package/dist/bridge/service.js.map +1 -1
- package/dist/codex/runner.d.ts +1 -0
- package/dist/codex/runner.js +3 -0
- package/dist/codex/runner.js.map +1 -1
- package/dist/collaboration/awareness.js +3 -2
- package/dist/collaboration/awareness.js.map +1 -1
- package/dist/collaboration/knowledge-gaps.d.ts +25 -0
- package/dist/collaboration/knowledge-gaps.js +158 -0
- package/dist/collaboration/knowledge-gaps.js.map +1 -0
- package/dist/collaboration/knowledge.js +20 -4
- package/dist/collaboration/knowledge.js.map +1 -1
- package/dist/collaboration/proactive-alerts.d.ts +39 -0
- package/dist/collaboration/proactive-alerts.js +127 -0
- package/dist/collaboration/proactive-alerts.js.map +1 -0
- package/dist/config/schema.d.ts +32 -0
- package/dist/config/schema.js +17 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/feishu/client.d.ts +5 -0
- package/dist/feishu/client.js +44 -0
- package/dist/feishu/client.js.map +1 -1
- package/dist/feishu/text.d.ts +4 -0
- package/dist/feishu/text.js +17 -0
- package/dist/feishu/text.js.map +1 -1
- package/dist/memory/summarize.js +38 -6
- package/dist/memory/summarize.js.map +1 -1
- package/dist/observability/metrics.d.ts +1 -1
- package/dist/observability/metrics.js.map +1 -1
- package/dist/state/run-state-store.d.ts +1 -0
- package/dist/state/run-state-store.js +10 -5
- package/dist/state/run-state-store.js.map +1 -1
- 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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"}
|
package/dist/bridge/service.d.ts
CHANGED
|
@@ -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;
|
package/dist/bridge/service.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
'
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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(
|
|
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,
|
|
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(',')
|