@zhin.js/service-activity-feedback 1.0.0 → 1.0.2
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.md +12 -6
- package/lib/ai-event-binder.d.ts +16 -2
- package/lib/ai-event-binder.d.ts.map +1 -1
- package/lib/ai-event-binder.js +58 -9
- package/lib/ai-event-binder.js.map +1 -1
- package/lib/config.d.ts +4 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js.map +1 -1
- package/lib/executor.d.ts +15 -2
- package/lib/executor.d.ts.map +1 -1
- package/lib/executor.js +115 -1
- package/lib/executor.js.map +1 -1
- package/lib/index.d.ts +7 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -16
- package/lib/index.js.map +1 -1
- package/lib/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator.js +4 -1
- package/lib/orchestrator.js.map +1 -1
- package/package.json +30 -9
- package/plugin.ts +51 -0
- package/schema.json +12 -0
- package/src/ai-event-binder.ts +78 -12
- package/src/config.ts +4 -0
- package/src/executor.ts +131 -10
- package/src/index.ts +10 -24
- package/src/orchestrator.ts +3 -0
- package/lib/context.d.ts +0 -21
- package/lib/context.d.ts.map +0 -1
- package/lib/context.js +0 -65
- package/lib/context.js.map +0 -1
- package/lib/register-binder.d.ts +0 -7
- package/lib/register-binder.d.ts.map +0 -1
- package/lib/register-binder.js +0 -215
- package/lib/register-binder.js.map +0 -1
- package/plugin.yml +0 -2
package/plugin.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { definePlugin, outboundHostToken } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { getLogger } from '@zhin.js/logger';
|
|
3
|
+
import {
|
|
4
|
+
loadActivityFeedbackServiceConfig,
|
|
5
|
+
type ActivityFeedbackServiceConfig,
|
|
6
|
+
} from './src/config.js';
|
|
7
|
+
import {
|
|
8
|
+
bindActivityFeedbackToAIEventBus,
|
|
9
|
+
createActivityFeedbackOrchestratorForRuntime,
|
|
10
|
+
} from './src/ai-event-binder.js';
|
|
11
|
+
import {
|
|
12
|
+
createNoopEndpointAccess,
|
|
13
|
+
createOutboundEndpointAccess,
|
|
14
|
+
} from './src/executor.js';
|
|
15
|
+
|
|
16
|
+
const logger = getLogger('activity-feedback');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Activity Feedback service — Plugin Runtime entry.
|
|
20
|
+
*
|
|
21
|
+
* Subscribes AI lifecycle events via `activityFeedbackAiBus`.
|
|
22
|
+
* When Root provides `outboundHostToken`, typing/status text uses
|
|
23
|
+
* ImRuntime.sendEndpointMessage; otherwise phases no-op.
|
|
24
|
+
*/
|
|
25
|
+
export default definePlugin<ActivityFeedbackServiceConfig>({
|
|
26
|
+
name: 'activity-feedback',
|
|
27
|
+
metadata: {
|
|
28
|
+
displayName: 'Activity Feedback',
|
|
29
|
+
},
|
|
30
|
+
setup(context) {
|
|
31
|
+
const serviceConfig = loadActivityFeedbackServiceConfig(context.config.get());
|
|
32
|
+
if (serviceConfig.enabled === false) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const access = context.resources.has(outboundHostToken)
|
|
37
|
+
? createOutboundEndpointAccess(context.resources.use(outboundHostToken), logger)
|
|
38
|
+
: createNoopEndpointAccess();
|
|
39
|
+
|
|
40
|
+
const orchestrator = createActivityFeedbackOrchestratorForRuntime(
|
|
41
|
+
serviceConfig,
|
|
42
|
+
logger,
|
|
43
|
+
access,
|
|
44
|
+
);
|
|
45
|
+
const dispose = bindActivityFeedbackToAIEventBus(orchestrator);
|
|
46
|
+
context.lifecycle.add(() => {
|
|
47
|
+
logger.debug('[ActivityFeedback] Disposing Runtime AI event binder');
|
|
48
|
+
dispose();
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
});
|
package/schema.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"properties": {
|
|
6
|
+
"enabled": { "type": "boolean", "default": true },
|
|
7
|
+
"defaults": { "type": "object", "additionalProperties": true },
|
|
8
|
+
"platforms": { "type": "object", "additionalProperties": true },
|
|
9
|
+
"endpoints": { "type": "object", "additionalProperties": true },
|
|
10
|
+
"schedule": { "type": "object", "additionalProperties": true }
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/ai-event-binder.ts
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
import type { Plugin } from 'zhin.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
subscribeAIEvents,
|
|
4
|
+
subscribeAIEventsOnTarget,
|
|
5
|
+
activityFeedbackAiBus,
|
|
6
|
+
isActivityFeedbackEnabled,
|
|
7
|
+
type AIEventHandlers,
|
|
8
|
+
} from '@zhin.js/agent';
|
|
9
|
+
import { loadActivityFeedbackServiceConfig, type ActivityFeedbackServiceConfig } from './config.js';
|
|
4
10
|
import {
|
|
5
11
|
ActivityFeedbackExecutor,
|
|
12
|
+
createNoopEndpointAccess,
|
|
6
13
|
createRootEndpointAccess,
|
|
7
14
|
type ActivityFeedbackEndpointAccess,
|
|
8
15
|
} from './executor.js';
|
|
9
16
|
import { ActivityFeedbackOrchestrator } from './orchestrator.js';
|
|
10
17
|
import { ActivityFeedbackPolicy } from './policy.js';
|
|
11
|
-
import { loadActivityFeedbackServiceConfig } from './config.js';
|
|
12
18
|
|
|
13
|
-
export function
|
|
14
|
-
root: Plugin['root'],
|
|
19
|
+
export function createActivityFeedbackAIEventHandlers(
|
|
15
20
|
orchestrator: ActivityFeedbackOrchestrator,
|
|
16
|
-
):
|
|
17
|
-
return
|
|
18
|
-
onQueuedStart: (payload) =>
|
|
19
|
-
|
|
21
|
+
): AIEventHandlers {
|
|
22
|
+
return {
|
|
23
|
+
onQueuedStart: (payload) => {
|
|
24
|
+
if (!isActivityFeedbackEnabled(payload, 'queued')) return;
|
|
25
|
+
return orchestrator.startPhase(payload, 'queued', 'activity.queued.start');
|
|
26
|
+
},
|
|
27
|
+
onQueuedClear: (payload) => {
|
|
28
|
+
if (!isActivityFeedbackEnabled(payload, 'queued')) return;
|
|
29
|
+
return orchestrator.stopPhase(payload, 'queued', 'activity.queued.clear');
|
|
30
|
+
},
|
|
20
31
|
|
|
21
32
|
onProcessingStart: async (payload) => {
|
|
33
|
+
if (!isActivityFeedbackEnabled(payload, 'active')) return;
|
|
22
34
|
await orchestrator.stopPhase(payload, 'queued', 'processing.start');
|
|
23
35
|
await orchestrator.startPhase(payload, 'active', 'processing.start');
|
|
24
36
|
},
|
|
@@ -26,19 +38,25 @@ export function bindActivityFeedbackToAIEvents(
|
|
|
26
38
|
onTypingStart: async () => {},
|
|
27
39
|
|
|
28
40
|
onProcessingFinish: async (payload) => {
|
|
41
|
+
if (!isActivityFeedbackEnabled(payload, 'active')) return;
|
|
29
42
|
if (payload.keepTyping) return;
|
|
30
43
|
await orchestrator.stopPhase(payload, 'thinking', 'processing.finish');
|
|
31
44
|
await orchestrator.stopPhase(payload, 'active', 'processing.finish');
|
|
32
45
|
},
|
|
33
46
|
|
|
34
47
|
onProcessingError: async (payload) => {
|
|
48
|
+
if (!isActivityFeedbackEnabled(payload, 'active')) return;
|
|
35
49
|
await orchestrator.stopPhase(payload, 'thinking', 'processing.error');
|
|
36
50
|
await orchestrator.stopPhase(payload, 'active', 'processing.error');
|
|
37
51
|
},
|
|
38
52
|
|
|
39
|
-
onTypingStop: (payload) =>
|
|
53
|
+
onTypingStop: (payload) => {
|
|
54
|
+
if (!isActivityFeedbackEnabled(payload, 'active')) return;
|
|
55
|
+
return orchestrator.stopPhase(payload, 'active', 'typing.stop');
|
|
56
|
+
},
|
|
40
57
|
|
|
41
58
|
onThinking: async (payload) => {
|
|
59
|
+
if (!isActivityFeedbackEnabled(payload, 'thinking')) return;
|
|
42
60
|
if (!payload.thinking) return;
|
|
43
61
|
await orchestrator.stopPhase(payload, 'active', 'thinking');
|
|
44
62
|
await orchestrator.startPhase(payload, 'thinking', 'thinking');
|
|
@@ -46,6 +64,7 @@ export function bindActivityFeedbackToAIEvents(
|
|
|
46
64
|
},
|
|
47
65
|
|
|
48
66
|
onSubagentStart: async (payload) => {
|
|
67
|
+
if (!isActivityFeedbackEnabled(payload, 'thinking')) return;
|
|
49
68
|
await orchestrator.stopPhase(payload, 'active', 'subagent.start');
|
|
50
69
|
await orchestrator.startPhase(payload, 'thinking', 'subagent.start');
|
|
51
70
|
const label = payload.label
|
|
@@ -55,10 +74,39 @@ export function bindActivityFeedbackToAIEvents(
|
|
|
55
74
|
},
|
|
56
75
|
|
|
57
76
|
onSubagentFinish: async (payload) => {
|
|
77
|
+
if (!isActivityFeedbackEnabled(payload, 'active')) return;
|
|
58
78
|
await orchestrator.stopPhase(payload, 'thinking', 'subagent.finish');
|
|
59
79
|
await orchestrator.startPhase(payload, 'active', 'subagent.finish');
|
|
60
80
|
},
|
|
61
|
-
|
|
81
|
+
|
|
82
|
+
onScheduleStart: (payload) => orchestrator.startPhase(payload, 'schedule_start', 'schedule.start'),
|
|
83
|
+
onScheduleFinish: (payload) => orchestrator.stopPhase(payload, 'schedule_start', 'schedule.finish'),
|
|
84
|
+
onScheduleError: async (payload) => {
|
|
85
|
+
await orchestrator.stopPhase(payload, 'schedule_start', 'schedule.error');
|
|
86
|
+
await orchestrator.startPhase(payload, 'schedule_error', 'schedule.error');
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Legacy host Plugin path (ALS-aware subscribeAIEvents). */
|
|
92
|
+
export function bindActivityFeedbackToAIEvents(
|
|
93
|
+
root: Plugin['root'],
|
|
94
|
+
orchestrator: ActivityFeedbackOrchestrator,
|
|
95
|
+
): () => void {
|
|
96
|
+
return subscribeAIEvents(root, createActivityFeedbackAIEventHandlers(orchestrator));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Plugin Runtime path: subscribe on module-level `activityFeedbackAiBus`
|
|
101
|
+
* (fed by ZhinAgentEventEmitter.emit). No usePlugin / Adapter inject.
|
|
102
|
+
*/
|
|
103
|
+
export function bindActivityFeedbackToAIEventBus(
|
|
104
|
+
orchestrator: ActivityFeedbackOrchestrator,
|
|
105
|
+
): () => void {
|
|
106
|
+
return subscribeAIEventsOnTarget(
|
|
107
|
+
activityFeedbackAiBus,
|
|
108
|
+
createActivityFeedbackAIEventHandlers(orchestrator),
|
|
109
|
+
);
|
|
62
110
|
}
|
|
63
111
|
|
|
64
112
|
export function mountActivityFeedbackService(
|
|
@@ -72,10 +120,15 @@ export function mountActivityFeedbackService(
|
|
|
72
120
|
});
|
|
73
121
|
}
|
|
74
122
|
|
|
123
|
+
export type ActivityFeedbackLogger = {
|
|
124
|
+
debug: (msg: string, ...args: unknown[]) => void;
|
|
125
|
+
error: (msg: string, ...args: unknown[]) => void;
|
|
126
|
+
};
|
|
127
|
+
|
|
75
128
|
export interface CreateActivityFeedbackOrchestratorOptions {
|
|
76
129
|
serviceConfig: ActivityFeedbackServiceConfig;
|
|
77
130
|
access: ActivityFeedbackEndpointAccess;
|
|
78
|
-
logger:
|
|
131
|
+
logger: ActivityFeedbackLogger;
|
|
79
132
|
}
|
|
80
133
|
|
|
81
134
|
export function createActivityFeedbackOrchestrator(
|
|
@@ -96,3 +149,16 @@ export function createActivityFeedbackOrchestratorFromPlugin(
|
|
|
96
149
|
logger: plugin.logger,
|
|
97
150
|
});
|
|
98
151
|
}
|
|
152
|
+
|
|
153
|
+
/** Runtime: prefer OutboundHost-backed access; else noop until Host wires outbound. */
|
|
154
|
+
export function createActivityFeedbackOrchestratorForRuntime(
|
|
155
|
+
serviceConfig: ActivityFeedbackServiceConfig,
|
|
156
|
+
logger: ActivityFeedbackLogger,
|
|
157
|
+
access: ActivityFeedbackEndpointAccess = createNoopEndpointAccess(),
|
|
158
|
+
): ActivityFeedbackOrchestrator {
|
|
159
|
+
return createActivityFeedbackOrchestrator({
|
|
160
|
+
serviceConfig,
|
|
161
|
+
access,
|
|
162
|
+
logger,
|
|
163
|
+
});
|
|
164
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -6,6 +6,10 @@ export interface ActivityFeedbackServiceConfig {
|
|
|
6
6
|
defaults?: ActivityFeedbackConfig;
|
|
7
7
|
platforms?: Record<string, ActivityFeedbackConfig>;
|
|
8
8
|
endpoints?: Record<string, ActivityFeedbackConfig>;
|
|
9
|
+
/** Schedule turn 专用相位配置 */
|
|
10
|
+
schedule?: {
|
|
11
|
+
phases?: Partial<Record<'start' | 'error' | 'finish', import('@zhin.js/agent').ActivityFeedbackScenePhases>>;
|
|
12
|
+
};
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
function mergePhaseScene(
|
package/src/executor.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { Adapter } from 'zhin.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
isGenericActivityFeedbackManager,
|
|
5
|
-
type ActivityFeedbackManager,
|
|
6
|
-
type ActivityFeedbackPhase,
|
|
7
|
-
type EndpointWithActivityFeedback,
|
|
8
|
-
type PlatformActivityFeedbackManager,
|
|
9
|
-
type ResolvedActivityFeedbackPhaseConfig,
|
|
10
|
-
} from '@zhin.js/agent';
|
|
11
|
-
import type { ActivityFeedbackEventContext } from '@zhin.js/agent';
|
|
2
|
+
import { enableActivityFeedbackForBot, isGenericActivityFeedbackManager, type ActivityFeedbackManager, type ActivityFeedbackPhase, type EndpointWithActivityFeedback, type PlatformActivityFeedbackManager, type ResolvedActivityFeedbackPhaseConfig, type ActivityFeedbackEventContext } from '@zhin.js/agent';
|
|
3
|
+
import type { OutboundHost } from '@zhin.js/plugin-runtime';
|
|
12
4
|
|
|
13
5
|
/** IM 侧 endpoint 访问 seam(便于测试注入 fake) */
|
|
14
6
|
export interface ActivityFeedbackEndpointAccess {
|
|
@@ -31,6 +23,135 @@ export function createRootEndpointAccess(root: {
|
|
|
31
23
|
};
|
|
32
24
|
}
|
|
33
25
|
|
|
26
|
+
/** Slice-2: no Adapter inject — executor start/stop no-op when resolve returns undefined. */
|
|
27
|
+
export function createNoopEndpointAccess(): ActivityFeedbackEndpointAccess {
|
|
28
|
+
return {
|
|
29
|
+
resolve() {
|
|
30
|
+
return undefined;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function stringifySendContent(content: unknown): string {
|
|
36
|
+
if (typeof content === 'string') return content;
|
|
37
|
+
if (!Array.isArray(content)) return content == null ? '' : String(content);
|
|
38
|
+
return content.map((segment) => {
|
|
39
|
+
if (typeof segment === 'string') return segment;
|
|
40
|
+
if (segment && typeof segment === 'object') {
|
|
41
|
+
const data = (segment as { data?: { text?: string }; text?: string }).data
|
|
42
|
+
?? (segment as { text?: string });
|
|
43
|
+
if (typeof data === 'object' && data && 'text' in data && typeof data.text === 'string') {
|
|
44
|
+
return data.text;
|
|
45
|
+
}
|
|
46
|
+
if (typeof (segment as { text?: string }).text === 'string') {
|
|
47
|
+
return (segment as { text: string }).text;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
}).join('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Plugin Runtime: resolve endpoints via OutboundHost → ImRuntime.sendEndpointMessage.
|
|
56
|
+
* Typing/reaction text goes through the unified outbound chain (no legacy Adapter.inject).
|
|
57
|
+
*
|
|
58
|
+
* 按 platform:endpointId 缓存 { endpoint, adapter }:activity manager 挂在
|
|
59
|
+
* endpoint.$activityFeedback 上,start/stop 必须解析到同一个对象,否则
|
|
60
|
+
* stop 时拿不到 manager,typing 指示器永远无法停止。
|
|
61
|
+
*/
|
|
62
|
+
export function createOutboundEndpointAccess(
|
|
63
|
+
outbound: OutboundHost,
|
|
64
|
+
logger?: { debug: (msg: string, ...args: unknown[]) => void },
|
|
65
|
+
): ActivityFeedbackEndpointAccess {
|
|
66
|
+
const cache = new Map<string, { endpoint: EndpointWithActivityFeedback; adapter: Adapter }>();
|
|
67
|
+
return {
|
|
68
|
+
resolve(platform, endpointId) {
|
|
69
|
+
const key = `${platform}:${endpointId}`;
|
|
70
|
+
const cached = cache.get(key);
|
|
71
|
+
if (cached) return cached;
|
|
72
|
+
const endpoint = {
|
|
73
|
+
$id: endpointId,
|
|
74
|
+
// Prefer real OutboundHost.recall when available (icqq RECALL_MSG).
|
|
75
|
+
$recallMessage: async (messageId: string) => {
|
|
76
|
+
if (outbound.recall) {
|
|
77
|
+
try {
|
|
78
|
+
await outbound.recall({ adapter: platform, endpointId, messageId });
|
|
79
|
+
return;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger?.debug(
|
|
82
|
+
`[ActivityFeedback] outbound recall failed (${key}):`,
|
|
83
|
+
error instanceof Error ? error.message : String(error),
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
logger?.debug(
|
|
89
|
+
`[ActivityFeedback] recall not supported via OutboundHost (${key}, messageId=${messageId})`,
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
$addReaction: outbound.addReaction
|
|
93
|
+
? async (
|
|
94
|
+
messageId: string,
|
|
95
|
+
emoji: string,
|
|
96
|
+
hint?: { sceneType?: 'private' | 'group' | 'channel'; channelId?: string },
|
|
97
|
+
) => outbound.addReaction!({
|
|
98
|
+
adapter: platform,
|
|
99
|
+
endpointId,
|
|
100
|
+
messageId,
|
|
101
|
+
emoji,
|
|
102
|
+
sceneType: hint?.sceneType,
|
|
103
|
+
channelId: hint?.channelId,
|
|
104
|
+
})
|
|
105
|
+
: undefined,
|
|
106
|
+
$removeReaction: outbound.removeReaction
|
|
107
|
+
? async (messageId: string, reactionId: string) => {
|
|
108
|
+
await outbound.removeReaction!({
|
|
109
|
+
adapter: platform,
|
|
110
|
+
endpointId,
|
|
111
|
+
messageId,
|
|
112
|
+
reactionId,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
: undefined,
|
|
116
|
+
} as EndpointWithActivityFeedback;
|
|
117
|
+
const adapter = {
|
|
118
|
+
sendMessage: async (options: {
|
|
119
|
+
type?: string;
|
|
120
|
+
id?: string;
|
|
121
|
+
content?: unknown;
|
|
122
|
+
}) => {
|
|
123
|
+
const text = stringifySendContent(options.content);
|
|
124
|
+
if (!text || !options.id) return null;
|
|
125
|
+
try {
|
|
126
|
+
const messageId = await outbound.send({
|
|
127
|
+
adapter: platform,
|
|
128
|
+
endpointId,
|
|
129
|
+
channelType: options.type || 'private',
|
|
130
|
+
channelId: options.id,
|
|
131
|
+
content: text,
|
|
132
|
+
});
|
|
133
|
+
// Prefer real id; fall back to a sentinel so MessageTypingIndicator
|
|
134
|
+
// keeps the phase active until stop (recall is a no-op here).
|
|
135
|
+
return messageId || `outbound:${Date.now()}`;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger?.debug(
|
|
138
|
+
`[ActivityFeedback] outbound send failed (${key}):`,
|
|
139
|
+
error instanceof Error ? error.message : String(error),
|
|
140
|
+
);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
endpoints: {
|
|
145
|
+
get: (id: string) => (id === endpointId ? endpoint : undefined),
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
const resolved = { endpoint, adapter: adapter as unknown as Adapter };
|
|
149
|
+
cache.set(key, resolved);
|
|
150
|
+
return resolved;
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
34
155
|
interface PhaseDriver {
|
|
35
156
|
start(
|
|
36
157
|
ctx: ActivityFeedbackEventContext,
|
package/src/index.ts
CHANGED
|
@@ -1,33 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zhin.js/service-activity-feedback
|
|
3
|
+
*
|
|
4
|
+
* Pure module exports. Plugin Runtime entry is `plugin.ts`.
|
|
3
5
|
*/
|
|
4
|
-
import { usePlugin } from 'zhin.js';
|
|
5
|
-
import {
|
|
6
|
-
createActivityFeedbackOrchestratorFromPlugin,
|
|
7
|
-
mountActivityFeedbackService,
|
|
8
|
-
} from './ai-event-binder.js';
|
|
9
|
-
import { loadActivityFeedbackServiceConfig, type ActivityFeedbackServiceConfig } from './config.js';
|
|
10
|
-
|
|
11
|
-
const plugin = usePlugin();
|
|
12
|
-
const { root, logger } = plugin;
|
|
13
|
-
|
|
14
|
-
const configService = root.inject('config');
|
|
15
|
-
const appConfig = configService?.getPrimary<{ activityFeedback?: ActivityFeedbackServiceConfig }>() || {};
|
|
16
|
-
const serviceConfig = loadActivityFeedbackServiceConfig(appConfig.activityFeedback);
|
|
17
|
-
|
|
18
|
-
if (serviceConfig.enabled !== false) {
|
|
19
|
-
mountActivityFeedbackService(
|
|
20
|
-
plugin,
|
|
21
|
-
createActivityFeedbackOrchestratorFromPlugin(plugin, serviceConfig),
|
|
22
|
-
);
|
|
23
|
-
} else {
|
|
24
|
-
logger.info('[ActivityFeedback] disabled by activityFeedback.enabled=false');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
6
|
export {
|
|
28
7
|
bindActivityFeedbackToAIEvents,
|
|
8
|
+
bindActivityFeedbackToAIEventBus,
|
|
9
|
+
createActivityFeedbackAIEventHandlers,
|
|
29
10
|
createActivityFeedbackOrchestrator,
|
|
30
11
|
createActivityFeedbackOrchestratorFromPlugin,
|
|
12
|
+
createActivityFeedbackOrchestratorForRuntime,
|
|
31
13
|
mountActivityFeedbackService,
|
|
32
14
|
} from './ai-event-binder.js';
|
|
33
15
|
export { ActivityFeedbackOrchestrator } from './orchestrator.js';
|
|
@@ -38,4 +20,8 @@ export {
|
|
|
38
20
|
type ActivityFeedbackServiceConfig,
|
|
39
21
|
} from './config.js';
|
|
40
22
|
export type { ActivityFeedbackEndpointAccess } from './executor.js';
|
|
41
|
-
export
|
|
23
|
+
export {
|
|
24
|
+
createNoopEndpointAccess,
|
|
25
|
+
createOutboundEndpointAccess,
|
|
26
|
+
createRootEndpointAccess,
|
|
27
|
+
} from './executor.js';
|
package/src/orchestrator.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
toActivityFeedbackEventContext,
|
|
3
|
+
isActivityFeedbackEnabled,
|
|
3
4
|
type AIEventPayload,
|
|
4
5
|
type ActivityFeedbackPhase,
|
|
5
6
|
} from '@zhin.js/agent';
|
|
@@ -16,6 +17,8 @@ export class ActivityFeedbackOrchestrator {
|
|
|
16
17
|
) {}
|
|
17
18
|
|
|
18
19
|
async startPhase(payload: AIEventPayload, phase: ActivityFeedbackPhase, reason: string): Promise<void> {
|
|
20
|
+
const gatePhase = phase as import('@zhin.js/agent').ActivityFeedbackGatePhase;
|
|
21
|
+
if (!isActivityFeedbackEnabled(payload, gatePhase)) return;
|
|
19
22
|
const ctx = toActivityFeedbackEventContext(payload);
|
|
20
23
|
if (!ctx) return;
|
|
21
24
|
|
package/lib/context.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { AIEventPayload, TypingIndicatorOptions } from '@zhin.js/agent';
|
|
2
|
-
export type ActivitySceneType = 'private' | 'group' | 'channel';
|
|
3
|
-
/** 单次 phase 操作的归一化上下文(由 AI 事件 payload 解析而来) */
|
|
4
|
-
export interface ActivityFeedbackContext {
|
|
5
|
-
platform: string;
|
|
6
|
-
endpointId: string;
|
|
7
|
-
sessionId: string;
|
|
8
|
-
messageId?: string;
|
|
9
|
-
sceneType: ActivitySceneType;
|
|
10
|
-
userId?: string;
|
|
11
|
-
groupId?: string;
|
|
12
|
-
options: TypingIndicatorOptions;
|
|
13
|
-
}
|
|
14
|
-
export declare function resolveSceneType(payload: AIEventPayload): ActivitySceneType;
|
|
15
|
-
export declare function resolveTargets(payload: AIEventPayload, sceneType: ActivitySceneType): {
|
|
16
|
-
userId?: string;
|
|
17
|
-
groupId?: string;
|
|
18
|
-
};
|
|
19
|
-
/** 将 AI 事件转为可执行的 activity 上下文;缺少定位信息时返回 null */
|
|
20
|
-
export declare function toActivityContext(payload: AIEventPayload): ActivityFeedbackContext | null;
|
|
21
|
-
//# sourceMappingURL=context.d.ts.map
|
package/lib/context.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE7E,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAEhE,+CAA+C;AAC/C,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,sBAAsB,CAAC;CACjC;AAID,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,iBAAiB,CAO3E;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,iBAAiB,GAC3B;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAwBvC;AAED,gDAAgD;AAChD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,uBAAuB,GAAG,IAAI,CAyBzF"}
|
package/lib/context.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const SYNTHETIC_SENDER_IDS = new Set(['system', 'cron', 'assistant']);
|
|
2
|
-
export function resolveSceneType(payload) {
|
|
3
|
-
const scope = payload.scope;
|
|
4
|
-
if (scope === 'group' || scope === 'channel' || scope === 'private')
|
|
5
|
-
return scope;
|
|
6
|
-
const sceneId = payload.sceneId ?? '';
|
|
7
|
-
if (sceneId.startsWith('group:'))
|
|
8
|
-
return 'group';
|
|
9
|
-
if (sceneId.startsWith('channel:'))
|
|
10
|
-
return 'channel';
|
|
11
|
-
return 'private';
|
|
12
|
-
}
|
|
13
|
-
export function resolveTargets(payload, sceneType) {
|
|
14
|
-
const { sceneId, userId, sessionId } = payload;
|
|
15
|
-
const parts = sessionId.split(':').filter((p) => p.length > 0);
|
|
16
|
-
let resolvedUserId = userId;
|
|
17
|
-
let groupId;
|
|
18
|
-
if (sceneType === 'group' || sceneType === 'channel') {
|
|
19
|
-
groupId = sceneId?.replace(/^(group|channel):/, '') || sceneId;
|
|
20
|
-
if (!groupId && parts.length >= 3)
|
|
21
|
-
groupId = parts[1];
|
|
22
|
-
if (!resolvedUserId && parts.length >= 3)
|
|
23
|
-
resolvedUserId = parts[parts.length - 1];
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
if (sceneId?.startsWith('private:'))
|
|
27
|
-
resolvedUserId = sceneId.slice('private:'.length);
|
|
28
|
-
else if ((!resolvedUserId || SYNTHETIC_SENDER_IDS.has(resolvedUserId))
|
|
29
|
-
&& sceneId
|
|
30
|
-
&& !SYNTHETIC_SENDER_IDS.has(sceneId)) {
|
|
31
|
-
resolvedUserId = sceneId;
|
|
32
|
-
}
|
|
33
|
-
else if (!resolvedUserId && parts.length >= 2) {
|
|
34
|
-
resolvedUserId = parts.length >= 3 ? parts[parts.length - 1] : parts[1];
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return { userId: resolvedUserId, groupId };
|
|
38
|
-
}
|
|
39
|
-
/** 将 AI 事件转为可执行的 activity 上下文;缺少定位信息时返回 null */
|
|
40
|
-
export function toActivityContext(payload) {
|
|
41
|
-
const { platform, endpointId, sessionId } = payload;
|
|
42
|
-
if (!platform || !endpointId)
|
|
43
|
-
return null;
|
|
44
|
-
const sceneType = resolveSceneType(payload);
|
|
45
|
-
const targets = resolveTargets(payload, sceneType);
|
|
46
|
-
return {
|
|
47
|
-
platform,
|
|
48
|
-
endpointId,
|
|
49
|
-
sessionId,
|
|
50
|
-
messageId: payload.messageId,
|
|
51
|
-
sceneType,
|
|
52
|
-
userId: targets.userId,
|
|
53
|
-
groupId: targets.groupId,
|
|
54
|
-
options: {
|
|
55
|
-
platform,
|
|
56
|
-
endpointId,
|
|
57
|
-
sessionId,
|
|
58
|
-
messageId: payload.messageId,
|
|
59
|
-
sceneType,
|
|
60
|
-
userId: targets.userId,
|
|
61
|
-
groupId: targets.groupId,
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=context.js.map
|
package/lib/context.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/lib/register-binder.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Activity Feedback — AI 生命周期事件绑定(服务插件)
|
|
3
|
-
*/
|
|
4
|
-
import type { Plugin } from 'zhin.js';
|
|
5
|
-
import { type ActivityFeedbackServiceConfig } from './config.js';
|
|
6
|
-
export declare function mountActivityFeedbackBinder(plugin: Plugin, serviceConfig: ActivityFeedbackServiceConfig): void;
|
|
7
|
-
//# sourceMappingURL=register-binder.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"register-binder.d.ts","sourceRoot":"","sources":["../src/register-binder.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAW,MAAM,EAAE,MAAM,SAAS,CAAC;AAc/C,OAAO,EACL,KAAK,6BAA6B,EAEnC,MAAM,aAAa,CAAC;AA6MrB,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,6BAA6B,GAC3C,IAAI,CA4DN"}
|