feique 1.1.4 → 1.2.1
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/dist/bridge/commands.d.ts +2 -0
- package/dist/bridge/commands.js +44 -7
- 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 +117 -4
- package/dist/bridge/service.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/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 +3 -0
- package/dist/config/schema.js +6 -0
- package/dist/config/schema.js.map +1 -1
- package/package.json +1 -1
package/dist/bridge/service.js
CHANGED
|
@@ -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',
|
|
@@ -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;
|
|
@@ -787,6 +838,14 @@ export class FeiqueService {
|
|
|
787
838
|
this.metrics?.recordTrustLevel(input.projectAlias, updated.current_level);
|
|
788
839
|
}
|
|
789
840
|
catch { /* trust tracking is best-effort */ }
|
|
841
|
+
// Proactive alerts: check if this run triggers any team alerts
|
|
842
|
+
try {
|
|
843
|
+
const completedRunState = await this.runStateStore.getRun(runId);
|
|
844
|
+
if (completedRunState) {
|
|
845
|
+
await this.checkAndSendAlerts(completedRunState);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
catch { /* alerts are best-effort */ }
|
|
790
849
|
// Direction 2: Auto-extract knowledge
|
|
791
850
|
if (this.config.service.memory_enabled && excerpt.length >= 100) {
|
|
792
851
|
try {
|
|
@@ -868,6 +927,14 @@ export class FeiqueService {
|
|
|
868
927
|
catch { /* trust tracking is best-effort */ }
|
|
869
928
|
// Notify project chats about the failure
|
|
870
929
|
await this.notifyProjectChats(input.projectAlias, `❌ 运行失败 [${input.projectAlias}]\n${message.slice(0, 200)}`);
|
|
930
|
+
// Proactive alerts on failure
|
|
931
|
+
try {
|
|
932
|
+
const failedRunState = await this.runStateStore.getRun(runId);
|
|
933
|
+
if (failedRunState) {
|
|
934
|
+
await this.checkAndSendAlerts(failedRunState);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
catch { /* alerts are best-effort */ }
|
|
871
938
|
}
|
|
872
939
|
if (cancelled) {
|
|
873
940
|
this.logger.warn({
|
|
@@ -1473,6 +1540,44 @@ export class FeiqueService {
|
|
|
1473
1540
|
const text = formatTeamDigest(digest);
|
|
1474
1541
|
await this.sendTextReply(context.chat_id, text, context.message_id, context.text);
|
|
1475
1542
|
}
|
|
1543
|
+
// ── Proactive Alerts ──
|
|
1544
|
+
async checkAndSendAlerts(completedRun) {
|
|
1545
|
+
const recentRuns = await this.runStateStore.listRuns();
|
|
1546
|
+
const projectConfig = this.config.projects[completedRun.project_alias];
|
|
1547
|
+
const dailyQuota = projectConfig?.daily_token_quota;
|
|
1548
|
+
const alerts = checkRunAlerts(completedRun, recentRuns, DEFAULT_ALERT_RULES, dailyQuota);
|
|
1549
|
+
if (alerts.length === 0)
|
|
1550
|
+
return;
|
|
1551
|
+
for (const alert of alerts) {
|
|
1552
|
+
const text = formatAlert(alert);
|
|
1553
|
+
// Send to admin chat IDs
|
|
1554
|
+
for (const chatId of this.config.security.admin_chat_ids) {
|
|
1555
|
+
try {
|
|
1556
|
+
await this.feishuClient.sendText(chatId, text);
|
|
1557
|
+
}
|
|
1558
|
+
catch { /* best-effort */ }
|
|
1559
|
+
}
|
|
1560
|
+
// Send to project notification channels
|
|
1561
|
+
await this.notifyProjectChats(alert.project_alias, text);
|
|
1562
|
+
await this.auditLog.append({
|
|
1563
|
+
type: 'collaboration.alert.sent',
|
|
1564
|
+
alert_kind: alert.kind,
|
|
1565
|
+
severity: alert.severity,
|
|
1566
|
+
project_alias: alert.project_alias,
|
|
1567
|
+
actor_id: alert.actor_id,
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
// ── Knowledge Gap Detection ──
|
|
1572
|
+
async handleGapsCommand(context) {
|
|
1573
|
+
const runs = await this.runStateStore.listRuns();
|
|
1574
|
+
const memories = this.config.service.memory_enabled
|
|
1575
|
+
? await this.memoryStore.listRecentMemories({ scope: 'project', project_alias: '' }, 200)
|
|
1576
|
+
: [];
|
|
1577
|
+
const gaps = detectKnowledgeGaps(runs, memories);
|
|
1578
|
+
const text = formatKnowledgeGaps(gaps);
|
|
1579
|
+
await this.sendTextReply(context.chat_id, text, context.message_id, context.text);
|
|
1580
|
+
}
|
|
1476
1581
|
// ── Direction 6: Timeline ──
|
|
1477
1582
|
async handleTimelineCommand(context, selectionKey, projectArg) {
|
|
1478
1583
|
const projectContext = await this.resolveProjectContext(context, selectionKey);
|
|
@@ -3274,9 +3379,17 @@ export class FeiqueService {
|
|
|
3274
3379
|
await this.sessionStore.dropProjectSession(conversationKey, projectAlias, session.thread_id);
|
|
3275
3380
|
}
|
|
3276
3381
|
}
|
|
3277
|
-
async sendTextReply(chatId, body, replyToMessageId, originalText, presentation) {
|
|
3382
|
+
async sendTextReply(chatId, body, replyToMessageId, originalText, presentation, mentionActor) {
|
|
3383
|
+
// In group chats, prepend @mention to the reply so the requester gets notified
|
|
3384
|
+
const actor = mentionActor ?? this.currentMessageContext;
|
|
3385
|
+
let mentionPrefix = '';
|
|
3386
|
+
if (actor?.chat_type === 'group' && actor.actor_id) {
|
|
3387
|
+
const displayName = actor.actor_name || actor.actor_id;
|
|
3388
|
+
mentionPrefix = `<at user_id="${actor.actor_id}">${displayName}</at>\n`;
|
|
3389
|
+
}
|
|
3390
|
+
const bodyWithMention = mentionPrefix ? mentionPrefix + body : body;
|
|
3278
3391
|
const title = this.buildReplyTitle(this.sanitizeUserVisibleReply(body));
|
|
3279
|
-
const formattedBody = this.sanitizeUserVisibleReply(this.formatQuotedReply(
|
|
3392
|
+
const formattedBody = this.sanitizeUserVisibleReply(this.formatQuotedReply(bodyWithMention, originalText));
|
|
3280
3393
|
if (this.config.service.reply_mode === 'card') {
|
|
3281
3394
|
const card = buildMessageCard({
|
|
3282
3395
|
title,
|
|
@@ -3318,7 +3431,7 @@ export class FeiqueService {
|
|
|
3318
3431
|
return response;
|
|
3319
3432
|
}
|
|
3320
3433
|
if (this.config.service.reply_quote_user_message && replyToMessageId) {
|
|
3321
|
-
const response = await this.feishuClient.sendText(chatId, this.sanitizeUserVisibleReply(
|
|
3434
|
+
const response = await this.feishuClient.sendText(chatId, this.sanitizeUserVisibleReply(bodyWithMention), { replyToMessageId });
|
|
3322
3435
|
await this.auditLog.append({
|
|
3323
3436
|
type: 'message.replied',
|
|
3324
3437
|
chat_id: chatId,
|