@zhin.js/service-activity-feedback 1.0.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/lib/policy.js ADDED
@@ -0,0 +1,24 @@
1
+ import { resolveActivityFeedbackPhaseConfig, } from '@zhin.js/agent';
2
+ import { mergeActivityFeedbackLayers, } from './config.js';
3
+ export { loadActivityFeedbackServiceConfig, resolveActivityFeedbackForTarget } from './config.js';
4
+ export class ActivityFeedbackPolicy {
5
+ service;
6
+ constructor(service) {
7
+ this.service = service;
8
+ }
9
+ resolvePhase(platform, endpointId, phase, sceneType) {
10
+ if (this.service.enabled === false) {
11
+ return { kind: 'disabled' };
12
+ }
13
+ const policy = mergeActivityFeedbackLayers(this.service.defaults, this.service.platforms?.[platform], this.service.endpoints?.[`${platform}:${endpointId}`]);
14
+ if (policy?.enabled === false) {
15
+ return { kind: 'disabled' };
16
+ }
17
+ const config = resolveActivityFeedbackPhaseConfig(platform, policy, phase, sceneType);
18
+ if (config.type === 'none') {
19
+ return { kind: 'none' };
20
+ }
21
+ return { kind: 'active', config };
22
+ }
23
+ }
24
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,GAInC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,2BAA2B,GAE5B,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,iCAAiC,EAAE,gCAAgC,EAAE,MAAM,aAAa,CAAC;AAOlG,MAAM,OAAO,sBAAsB;IACJ;IAA7B,YAA6B,OAAsC;QAAtC,YAAO,GAAP,OAAO,CAA+B;IAAG,CAAC;IAEvE,YAAY,CACV,QAAgB,EAChB,UAAkB,EAClB,KAA4B,EAC5B,SAA4B;QAE5B,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;YACnC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC9B,CAAC;QAED,MAAM,MAAM,GAAG,2BAA2B,CACxC,IAAI,CAAC,OAAO,CAAC,QAAQ,EACrB,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,EAClC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,GAAG,QAAQ,IAAI,UAAU,EAAE,CAAC,CACtD,CAAC;QAEF,IAAI,MAAM,EAAE,OAAO,KAAK,KAAK,EAAE,CAAC;YAC9B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC9B,CAAC;QAED,MAAM,MAAM,GAAG,kCAAkC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QACtF,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACpC,CAAC;CACF"}
@@ -0,0 +1,7 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,215 @@
1
+ import { subscribeAIEvents, enableActivityFeedbackForBot, isGenericActivityFeedbackManager, resolveActivityFeedbackPhaseConfig, } from '@zhin.js/agent';
2
+ import { resolveActivityFeedbackForTarget, } from './config.js';
3
+ const SYNTHETIC_SENDER_IDS = new Set(['system', 'cron', 'assistant']);
4
+ function resolveSceneType(payload) {
5
+ const scope = payload.scope;
6
+ if (scope === 'group' || scope === 'channel' || scope === 'private')
7
+ return scope;
8
+ const sceneId = payload.sceneId ?? '';
9
+ if (sceneId.startsWith('group:'))
10
+ return 'group';
11
+ if (sceneId.startsWith('channel:'))
12
+ return 'channel';
13
+ return 'private';
14
+ }
15
+ function resolveTargets(payload, sceneType) {
16
+ const { sceneId, userId, sessionId } = payload;
17
+ const parts = sessionId.split(':').filter((p) => p.length > 0);
18
+ let resolvedUserId = userId;
19
+ let groupId;
20
+ if (sceneType === 'group' || sceneType === 'channel') {
21
+ groupId = sceneId?.replace(/^(group|channel):/, '') || sceneId;
22
+ if (!groupId && parts.length >= 3)
23
+ groupId = parts[1];
24
+ if (!resolvedUserId && parts.length >= 3)
25
+ resolvedUserId = parts[parts.length - 1];
26
+ }
27
+ else {
28
+ if (sceneId?.startsWith('private:'))
29
+ resolvedUserId = sceneId.slice('private:'.length);
30
+ else if ((!resolvedUserId || SYNTHETIC_SENDER_IDS.has(resolvedUserId))
31
+ && sceneId
32
+ && !SYNTHETIC_SENDER_IDS.has(sceneId)) {
33
+ resolvedUserId = sceneId;
34
+ }
35
+ else if (!resolvedUserId && parts.length >= 2) {
36
+ resolvedUserId = parts.length >= 3 ? parts[parts.length - 1] : parts[1];
37
+ }
38
+ }
39
+ return { userId: resolvedUserId, groupId };
40
+ }
41
+ function buildIndicatorOptions(payload, sceneType) {
42
+ const targets = resolveTargets(payload, sceneType);
43
+ return {
44
+ platform: payload.platform,
45
+ endpointId: payload.endpointId,
46
+ sessionId: payload.sessionId,
47
+ messageId: payload.messageId,
48
+ sceneType,
49
+ userId: targets.userId,
50
+ groupId: targets.groupId,
51
+ };
52
+ }
53
+ function syncPolicyToPlatformManager(endpoint, policy) {
54
+ const manager = endpoint.$activityFeedback;
55
+ if (!manager || isGenericActivityFeedbackManager(manager))
56
+ return;
57
+ const withUpdate = manager;
58
+ withUpdate.updateConfig?.(policy ?? {});
59
+ }
60
+ async function resolveGenericManager(root, endpoint, platform, adapter) {
61
+ if (endpoint.$activityFeedback && isGenericActivityFeedbackManager(endpoint.$activityFeedback)) {
62
+ return endpoint.$activityFeedback;
63
+ }
64
+ return enableActivityFeedbackForBot(endpoint, platform, adapter);
65
+ }
66
+ function resolvePolicy(serviceConfig, platform, endpointId) {
67
+ return resolveActivityFeedbackForTarget(serviceConfig, platform, endpointId);
68
+ }
69
+ async function startPhase(root, logger, serviceConfig, payload, phase, reason) {
70
+ const { platform, endpointId } = payload;
71
+ if (!platform || !endpointId)
72
+ return;
73
+ try {
74
+ const adapter = root.injectAdapter(platform);
75
+ const endpoint = adapter?.endpoints?.get(endpointId);
76
+ if (!endpoint)
77
+ return;
78
+ const activityConfig = resolvePolicy(serviceConfig, platform, endpointId);
79
+ if (activityConfig?.enabled === false)
80
+ return;
81
+ const sceneType = resolveSceneType(payload);
82
+ const phaseConfig = resolveActivityFeedbackPhaseConfig(platform, activityConfig, phase, sceneType);
83
+ if (phaseConfig.type === 'none')
84
+ return;
85
+ const options = buildIndicatorOptions(payload, sceneType);
86
+ syncPolicyToPlatformManager(endpoint, activityConfig);
87
+ if (phase === 'active'
88
+ && endpoint.$activityFeedback
89
+ && !isGenericActivityFeedbackManager(endpoint.$activityFeedback)) {
90
+ const platformManager = endpoint.$activityFeedback;
91
+ const targets = resolveTargets(payload, sceneType);
92
+ await platformManager.start({
93
+ messageId: payload.messageId,
94
+ sessionId: payload.sessionId,
95
+ userId: targets.userId,
96
+ groupId: targets.groupId,
97
+ sceneType: sceneType === 'channel' ? 'group' : sceneType,
98
+ phase,
99
+ });
100
+ return;
101
+ }
102
+ const manager = await resolveGenericManager(root, endpoint, platform, adapter);
103
+ const existing = manager.getActiveIndicator(phase, options);
104
+ if (existing)
105
+ return;
106
+ logger.debug(`[ActivityFeedback] start ${phase} (${reason}) session=${payload.sessionId}`);
107
+ await manager.start(phase, options, phaseConfig);
108
+ }
109
+ catch (error) {
110
+ logger.error(`[ActivityFeedback] start ${phase} failed (${reason}):`, error);
111
+ }
112
+ }
113
+ async function stopPhase(root, logger, payload, phase, reason) {
114
+ const { platform, endpointId } = payload;
115
+ if (!platform || !endpointId)
116
+ return;
117
+ try {
118
+ const adapter = root.injectAdapter(platform);
119
+ const endpoint = adapter?.endpoints?.get(endpointId);
120
+ if (!endpoint?.$activityFeedback)
121
+ return;
122
+ const sceneType = resolveSceneType(payload);
123
+ const options = buildIndicatorOptions(payload, sceneType);
124
+ if (phase === 'active' && !isGenericActivityFeedbackManager(endpoint.$activityFeedback)) {
125
+ const targets = resolveTargets(payload, sceneType);
126
+ await endpoint.$activityFeedback.stop({
127
+ sessionId: payload.sessionId,
128
+ userId: targets.userId,
129
+ groupId: targets.groupId,
130
+ phase,
131
+ });
132
+ return;
133
+ }
134
+ if (!isGenericActivityFeedbackManager(endpoint.$activityFeedback))
135
+ return;
136
+ logger.debug(`[ActivityFeedback] stop ${phase} (${reason}) session=${payload.sessionId}`);
137
+ await endpoint.$activityFeedback.stop(phase, options);
138
+ }
139
+ catch (error) {
140
+ logger.error(`[ActivityFeedback] stop ${phase} failed (${reason}):`, error);
141
+ }
142
+ }
143
+ async function updateThinkingPhase(root, logger, payload, text) {
144
+ const { platform, endpointId } = payload;
145
+ if (!platform || !endpointId || !text)
146
+ return;
147
+ try {
148
+ const adapter = root.injectAdapter(platform);
149
+ const endpoint = adapter?.endpoints?.get(endpointId);
150
+ if (!endpoint?.$activityFeedback || !isGenericActivityFeedbackManager(endpoint.$activityFeedback))
151
+ return;
152
+ const sceneType = resolveSceneType(payload);
153
+ const options = buildIndicatorOptions(payload, sceneType);
154
+ const indicator = endpoint.$activityFeedback.getActiveIndicator('thinking', options);
155
+ if (indicator?.update) {
156
+ await indicator.update(text);
157
+ }
158
+ }
159
+ catch (error) {
160
+ logger.error('[ActivityFeedback] thinking update failed:', error);
161
+ }
162
+ }
163
+ export function mountActivityFeedbackBinder(plugin, serviceConfig) {
164
+ const { root, logger } = plugin;
165
+ const dispose = subscribeAIEvents(root, {
166
+ onQueuedStart: async (payload) => {
167
+ await startPhase(root, logger, serviceConfig, payload, 'queued', 'activity.queued.start');
168
+ },
169
+ onQueuedClear: async (payload) => {
170
+ await stopPhase(root, logger, payload, 'queued', 'activity.queued.clear');
171
+ },
172
+ onProcessingStart: async (payload) => {
173
+ await stopPhase(root, logger, payload, 'queued', 'processing.start');
174
+ await startPhase(root, logger, serviceConfig, payload, 'active', 'processing.start');
175
+ },
176
+ onTypingStart: async () => {
177
+ // active 由 processing.start 统一拉起
178
+ },
179
+ onProcessingFinish: async (payload) => {
180
+ if (payload.keepTyping)
181
+ return;
182
+ await stopPhase(root, logger, payload, 'thinking', 'processing.finish');
183
+ await stopPhase(root, logger, payload, 'active', 'processing.finish');
184
+ },
185
+ onProcessingError: async (payload) => {
186
+ await stopPhase(root, logger, payload, 'thinking', 'processing.error');
187
+ await stopPhase(root, logger, payload, 'active', 'processing.error');
188
+ },
189
+ onTypingStop: async (payload) => {
190
+ await stopPhase(root, logger, payload, 'active', 'typing.stop');
191
+ },
192
+ onThinking: async (payload) => {
193
+ if (!payload.thinking)
194
+ return;
195
+ await stopPhase(root, logger, payload, 'active', 'thinking');
196
+ await startPhase(root, logger, serviceConfig, payload, 'thinking', 'thinking');
197
+ await updateThinkingPhase(root, logger, payload, payload.thinking);
198
+ },
199
+ onSubagentStart: async (payload) => {
200
+ await stopPhase(root, logger, payload, 'active', 'subagent.start');
201
+ await startPhase(root, logger, serviceConfig, payload, 'thinking', 'subagent.start');
202
+ const label = payload.label ? `🔍 子任务执行中: ${payload.label}...` : '🔍 子 agent 处理中...';
203
+ await updateThinkingPhase(root, logger, payload, label);
204
+ },
205
+ onSubagentFinish: async (payload) => {
206
+ await stopPhase(root, logger, payload, 'thinking', 'subagent.finish');
207
+ await startPhase(root, logger, serviceConfig, payload, 'active', 'subagent.finish');
208
+ },
209
+ });
210
+ plugin.onDispose(() => {
211
+ logger.debug('[ActivityFeedback] Disposing binder');
212
+ dispose();
213
+ });
214
+ }
215
+ //# sourceMappingURL=register-binder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-binder.js","sourceRoot":"","sources":["../src/register-binder.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,iBAAiB,EAIjB,4BAA4B,EAC5B,gCAAgC,EAIhC,kCAAkC,GAEnC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,gCAAgC,GACjC,MAAM,aAAa,CAAC;AAErB,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;AAEtE,SAAS,gBAAgB,CAAC,OAAuB;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAClF,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IACtC,IAAI,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAC;IACjD,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAC;IACrD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,cAAc,CAAC,OAAuB,EAAE,SAA0C;IACzF,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAC/C,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/D,IAAI,cAAc,GAAG,MAAM,CAAC;IAC5B,IAAI,OAA2B,CAAC;IAEhC,IAAI,SAAS,KAAK,OAAO,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QACrD,OAAO,GAAG,OAAO,EAAE,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC;QAC/D,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;YAAE,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;YAAE,cAAc,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrF,CAAC;SAAM,CAAC;QACN,IAAI,OAAO,EAAE,UAAU,CAAC,UAAU,CAAC;YAAE,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;aAClF,IACH,CAAC,CAAC,cAAc,IAAI,oBAAoB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;eAC1D,OAAO;eACP,CAAC,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,EACrC,CAAC;YACD,cAAc,GAAG,OAAO,CAAC;QAC3B,CAAC;aAAM,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YAChD,cAAc,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,qBAAqB,CAC5B,OAAuB,EACvB,SAA0C;IAE1C,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACnD,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,QAAS;QAC3B,UAAU,EAAE,OAAO,CAAC,UAAW;QAC/B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS;QACT,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC;AACJ,CAAC;AAED,SAAS,2BAA2B,CAClC,QAAsC,EACtC,MAA0C;IAE1C,MAAM,OAAO,GAAG,QAAQ,CAAC,iBAAiB,CAAC;IAC3C,IAAI,CAAC,OAAO,IAAI,gCAAgC,CAAC,OAAO,CAAC;QAAE,OAAO;IAClE,MAAM,UAAU,GAAG,OAElB,CAAC;IACF,UAAU,CAAC,YAAY,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,IAAY,EACZ,QAAsC,EACtC,QAAgB,EAChB,OAAgB;IAEhB,IAAI,QAAQ,CAAC,iBAAiB,IAAI,gCAAgC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;QAC/F,OAAO,QAAQ,CAAC,iBAAiB,CAAC;IACpC,CAAC;IACD,OAAO,4BAA4B,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,aAAa,CACpB,aAA4C,EAC5C,QAAgB,EAChB,UAAkB;IAElB,OAAO,gCAAgC,CAAC,aAAa,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;AAC/E,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,IAAY,EACZ,MAAwB,EACxB,aAA4C,EAC5C,OAAuB,EACvB,KAA4B,EAC5B,MAAc;IAEd,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACzC,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU;QAAE,OAAO;IAErC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,CAA6C,CAAC;QACjG,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,MAAM,cAAc,GAAG,aAAa,CAAC,aAAa,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QAC1E,IAAI,cAAc,EAAE,OAAO,KAAK,KAAK;YAAE,OAAO;QAE9C,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,WAAW,GAAG,kCAAkC,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QACnG,IAAI,WAAW,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO;QAExC,MAAM,OAAO,GAAG,qBAAqB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1D,2BAA2B,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAEtD,IACE,KAAK,KAAK,QAAQ;eACf,QAAQ,CAAC,iBAAiB;eAC1B,CAAC,gCAAgC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAChE,CAAC;YACD,MAAM,eAAe,GAAG,QAAQ,CAAC,iBAAoD,CAAC;YACtF,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YACnD,MAAM,eAAe,CAAC,KAAK,CAAC;gBAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,SAAS,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;gBACxD,KAAK;aACN,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAQ,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC5D,IAAI,QAAQ;YAAE,OAAO;QAErB,MAAM,CAAC,KAAK,CAAC,4BAA4B,KAAK,KAAK,MAAM,aAAa,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;QAC3F,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,4BAA4B,KAAK,YAAY,MAAM,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,IAAY,EACZ,MAAwB,EACxB,OAAuB,EACvB,KAA4B,EAC5B,MAAc;IAEd,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACzC,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU;QAAE,OAAO;IAErC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,CAA6C,CAAC;QACjG,IAAI,CAAC,QAAQ,EAAE,iBAAiB;YAAE,OAAO;QAEzC,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,qBAAqB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAE1D,IAAI,KAAK,KAAK,QAAQ,IAAI,CAAC,gCAAgC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACxF,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YACnD,MAAO,QAAQ,CAAC,iBAAqD,CAAC,IAAI,CAAC;gBACzE,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,KAAK;aACN,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,CAAC,gCAAgC,CAAC,QAAQ,CAAC,iBAAiB,CAAC;YAAE,OAAO;QAE1E,MAAM,CAAC,KAAK,CAAC,2BAA2B,KAAK,KAAK,MAAM,aAAa,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;QAC1F,MAAM,QAAQ,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,2BAA2B,KAAK,YAAY,MAAM,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,IAAY,EACZ,MAAwB,EACxB,OAAuB,EACvB,IAAY;IAEZ,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACzC,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI;QAAE,OAAO;IAE9C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,CAA6C,CAAC;QACjG,IAAI,CAAC,QAAQ,EAAE,iBAAiB,IAAI,CAAC,gCAAgC,CAAC,QAAQ,CAAC,iBAAiB,CAAC;YAAE,OAAO;QAE1G,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,qBAAqB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1D,MAAM,SAAS,GAAG,QAAQ,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACrF,IAAI,SAAS,EAAE,MAAM,EAAE,CAAC;YACtB,MAAM,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,MAAc,EACd,aAA4C;IAE5C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAEhC,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,EAAE;QACtC,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC/B,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,uBAAuB,CAAC,CAAC;QAC5F,CAAC;QAED,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC/B,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,uBAAuB,CAAC,CAAC;QAC5E,CAAC;QAED,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACnC,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC;YACrE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QACvF,CAAC;QAED,aAAa,EAAE,KAAK,IAAI,EAAE;YACxB,iCAAiC;QACnC,CAAC;QAED,kBAAkB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACpC,IAAI,OAAO,CAAC,UAAU;gBAAE,OAAO;YAC/B,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC;YACxE,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QACxE,CAAC;QAED,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACnC,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;YACvE,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QACvE,CAAC;QAED,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC9B,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;QAClE,CAAC;QAED,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC5B,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAAE,OAAO;YAC9B,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC7D,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;YAC/E,MAAM,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QACrE,CAAC;QAED,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACjC,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;YACnE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC;YACrF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,OAAO,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC;YACrF,MAAM,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1D,CAAC;QAED,gBAAgB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAClC,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC;YACtE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QACtF,CAAC;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE;QACpB,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACpD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@zhin.js/service-activity-feedback",
3
+ "version": "1.0.0",
4
+ "description": "Activity Feedback 服务插件:订阅 AI 生命周期事件,按 endpoint activityFeedback 配置驱动适配器能力",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "development": "./src/index.ts",
12
+ "import": "./lib/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "lib",
19
+ "plugin.yml",
20
+ "README.md"
21
+ ],
22
+ "keywords": [
23
+ "zhin",
24
+ "zhin.js",
25
+ "plugin",
26
+ "service",
27
+ "activity-feedback",
28
+ "typing-indicator"
29
+ ],
30
+ "author": "Zhin.js Team",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/zhinjs/zhin.git",
35
+ "directory": "plugins/services/activity-feedback"
36
+ },
37
+ "dependencies": {
38
+ "@zhin.js/agent": "1.0.1"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^25.9.2",
42
+ "typescript": "^6.0.3",
43
+ "zhin.js": "4.1.0"
44
+ },
45
+ "peerDependencies": {
46
+ "@zhin.js/agent": "1.0.1",
47
+ "zhin.js": "4.1.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc",
51
+ "clean": "rimraf lib",
52
+ "test": "vitest run --config ../../../vitest.config.ts plugins/services/activity-feedback/tests"
53
+ }
54
+ }
package/plugin.yml ADDED
@@ -0,0 +1,2 @@
1
+ name: service-activity-feedback
2
+ description: Activity Feedback 服务 — 订阅 AI 事件,按 endpoint 配置驱动 queued/active/thinking 三阶段反馈
@@ -0,0 +1,98 @@
1
+ import type { Plugin } from 'zhin.js';
2
+ import { subscribeAIEvents } from '@zhin.js/agent';
3
+ import type { ActivityFeedbackServiceConfig } from './config.js';
4
+ import {
5
+ ActivityFeedbackExecutor,
6
+ createRootEndpointAccess,
7
+ type ActivityFeedbackEndpointAccess,
8
+ } from './executor.js';
9
+ import { ActivityFeedbackOrchestrator } from './orchestrator.js';
10
+ import { ActivityFeedbackPolicy } from './policy.js';
11
+ import { loadActivityFeedbackServiceConfig } from './config.js';
12
+
13
+ export function bindActivityFeedbackToAIEvents(
14
+ root: Plugin['root'],
15
+ orchestrator: ActivityFeedbackOrchestrator,
16
+ ): () => void {
17
+ return subscribeAIEvents(root, {
18
+ onQueuedStart: (payload) => orchestrator.startPhase(payload, 'queued', 'activity.queued.start'),
19
+ onQueuedClear: (payload) => orchestrator.stopPhase(payload, 'queued', 'activity.queued.clear'),
20
+
21
+ onProcessingStart: async (payload) => {
22
+ await orchestrator.stopPhase(payload, 'queued', 'processing.start');
23
+ await orchestrator.startPhase(payload, 'active', 'processing.start');
24
+ },
25
+
26
+ onTypingStart: async () => {},
27
+
28
+ onProcessingFinish: async (payload) => {
29
+ if (payload.keepTyping) return;
30
+ await orchestrator.stopPhase(payload, 'thinking', 'processing.finish');
31
+ await orchestrator.stopPhase(payload, 'active', 'processing.finish');
32
+ },
33
+
34
+ onProcessingError: async (payload) => {
35
+ await orchestrator.stopPhase(payload, 'thinking', 'processing.error');
36
+ await orchestrator.stopPhase(payload, 'active', 'processing.error');
37
+ },
38
+
39
+ onTypingStop: (payload) => orchestrator.stopPhase(payload, 'active', 'typing.stop'),
40
+
41
+ onThinking: async (payload) => {
42
+ if (!payload.thinking) return;
43
+ await orchestrator.stopPhase(payload, 'active', 'thinking');
44
+ await orchestrator.startPhase(payload, 'thinking', 'thinking');
45
+ await orchestrator.updateThinkingText(payload, payload.thinking);
46
+ },
47
+
48
+ onSubagentStart: async (payload) => {
49
+ await orchestrator.stopPhase(payload, 'active', 'subagent.start');
50
+ await orchestrator.startPhase(payload, 'thinking', 'subagent.start');
51
+ const label = payload.label
52
+ ? `🔍 子任务执行中: ${payload.label}...`
53
+ : '🔍 子 agent 处理中...';
54
+ await orchestrator.updateThinkingText(payload, label);
55
+ },
56
+
57
+ onSubagentFinish: async (payload) => {
58
+ await orchestrator.stopPhase(payload, 'thinking', 'subagent.finish');
59
+ await orchestrator.startPhase(payload, 'active', 'subagent.finish');
60
+ },
61
+ });
62
+ }
63
+
64
+ export function mountActivityFeedbackService(
65
+ plugin: Plugin,
66
+ orchestrator: ActivityFeedbackOrchestrator,
67
+ ): void {
68
+ const dispose = bindActivityFeedbackToAIEvents(plugin.root, orchestrator);
69
+ plugin.onDispose(() => {
70
+ plugin.logger.debug('[ActivityFeedback] Disposing binder');
71
+ dispose();
72
+ });
73
+ }
74
+
75
+ export interface CreateActivityFeedbackOrchestratorOptions {
76
+ serviceConfig: ActivityFeedbackServiceConfig;
77
+ access: ActivityFeedbackEndpointAccess;
78
+ logger: Plugin['logger'];
79
+ }
80
+
81
+ export function createActivityFeedbackOrchestrator(
82
+ options: CreateActivityFeedbackOrchestratorOptions,
83
+ ): ActivityFeedbackOrchestrator {
84
+ const policy = new ActivityFeedbackPolicy(loadActivityFeedbackServiceConfig(options.serviceConfig));
85
+ const executor = new ActivityFeedbackExecutor(options.access);
86
+ return new ActivityFeedbackOrchestrator(policy, executor, options.logger);
87
+ }
88
+
89
+ export function createActivityFeedbackOrchestratorFromPlugin(
90
+ plugin: Plugin,
91
+ serviceConfig: ActivityFeedbackServiceConfig,
92
+ ): ActivityFeedbackOrchestrator {
93
+ return createActivityFeedbackOrchestrator({
94
+ serviceConfig,
95
+ access: createRootEndpointAccess(plugin.root),
96
+ logger: plugin.logger,
97
+ });
98
+ }
package/src/config.ts ADDED
@@ -0,0 +1,75 @@
1
+ import type { ActivityFeedbackConfig } from '@zhin.js/agent';
2
+
3
+ /** 顶层 `activityFeedback` 配置(zhin.config.yml,与 endpoints 解耦) */
4
+ export interface ActivityFeedbackServiceConfig {
5
+ enabled?: boolean;
6
+ defaults?: ActivityFeedbackConfig;
7
+ platforms?: Record<string, ActivityFeedbackConfig>;
8
+ endpoints?: Record<string, ActivityFeedbackConfig>;
9
+ }
10
+
11
+ function mergePhaseScene(
12
+ base: ActivityFeedbackConfig['phases'],
13
+ override: ActivityFeedbackConfig['phases'],
14
+ ): ActivityFeedbackConfig['phases'] {
15
+ if (!base && !override) return undefined;
16
+ const phases = new Set([
17
+ ...Object.keys(base ?? {}),
18
+ ...Object.keys(override ?? {}),
19
+ ]) as Set<keyof NonNullable<ActivityFeedbackConfig['phases']>>;
20
+
21
+ const merged: NonNullable<ActivityFeedbackConfig['phases']> = {};
22
+ for (const phase of phases) {
23
+ merged[phase] = {
24
+ ...base?.[phase],
25
+ ...override?.[phase],
26
+ private: { ...base?.[phase]?.private, ...override?.[phase]?.private },
27
+ group: { ...base?.[phase]?.group, ...override?.[phase]?.group },
28
+ channel: { ...base?.[phase]?.channel, ...override?.[phase]?.channel },
29
+ };
30
+ }
31
+ return merged;
32
+ }
33
+
34
+ export function mergeActivityFeedbackLayers(
35
+ ...layers: (ActivityFeedbackConfig | undefined)[]
36
+ ): ActivityFeedbackConfig | undefined {
37
+ let result: ActivityFeedbackConfig | undefined;
38
+ for (const layer of layers) {
39
+ if (!layer) continue;
40
+ if (!result) {
41
+ result = { ...layer, phases: layer.phases ? { ...layer.phases } : undefined };
42
+ continue;
43
+ }
44
+ if (layer.enabled !== undefined) result.enabled = layer.enabled;
45
+ if (layer.phases) {
46
+ result.phases = mergePhaseScene(result.phases, layer.phases);
47
+ }
48
+ }
49
+ return result;
50
+ }
51
+
52
+ export function loadActivityFeedbackServiceConfig(
53
+ raw: ActivityFeedbackServiceConfig | undefined,
54
+ ): ActivityFeedbackServiceConfig {
55
+ return {
56
+ enabled: true,
57
+ ...raw,
58
+ };
59
+ }
60
+
61
+ /** @deprecated 使用 ActivityFeedbackPolicy */
62
+ export function resolveActivityFeedbackForTarget(
63
+ service: ActivityFeedbackServiceConfig,
64
+ platform: string,
65
+ endpointId: string,
66
+ ): ActivityFeedbackConfig | undefined {
67
+ if (service.enabled === false) {
68
+ return { enabled: false };
69
+ }
70
+ return mergeActivityFeedbackLayers(
71
+ service.defaults,
72
+ service.platforms?.[platform],
73
+ service.endpoints?.[`${platform}:${endpointId}`],
74
+ );
75
+ }
@@ -0,0 +1,166 @@
1
+ import type { Adapter } from 'zhin.js';
2
+ import {
3
+ enableActivityFeedbackForBot,
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';
12
+
13
+ /** IM 侧 endpoint 访问 seam(便于测试注入 fake) */
14
+ export interface ActivityFeedbackEndpointAccess {
15
+ resolve(
16
+ platform: string,
17
+ endpointId: string,
18
+ ): { endpoint: EndpointWithActivityFeedback; adapter: Adapter } | undefined;
19
+ }
20
+
21
+ export function createRootEndpointAccess(root: {
22
+ injectAdapter(platform: string): Adapter | undefined;
23
+ }): ActivityFeedbackEndpointAccess {
24
+ return {
25
+ resolve(platform, endpointId) {
26
+ const adapter = root.injectAdapter(platform);
27
+ const endpoint = adapter?.endpoints?.get(endpointId) as EndpointWithActivityFeedback | undefined;
28
+ if (!endpoint || !adapter) return undefined;
29
+ return { endpoint, adapter };
30
+ },
31
+ };
32
+ }
33
+
34
+ interface PhaseDriver {
35
+ start(
36
+ ctx: ActivityFeedbackEventContext,
37
+ phase: ActivityFeedbackPhase,
38
+ phaseConfig: ResolvedActivityFeedbackPhaseConfig,
39
+ ): Promise<void>;
40
+ stop(ctx: ActivityFeedbackEventContext, phase: ActivityFeedbackPhase): Promise<void>;
41
+ updateThinkingText(ctx: ActivityFeedbackEventContext, text: string): Promise<void>;
42
+ }
43
+
44
+ class PlatformPhaseDriver implements PhaseDriver {
45
+ constructor(private readonly manager: PlatformActivityFeedbackManager) {}
46
+
47
+ async start(
48
+ ctx: ActivityFeedbackEventContext,
49
+ phase: ActivityFeedbackPhase,
50
+ phaseConfig: ResolvedActivityFeedbackPhaseConfig,
51
+ ): Promise<void> {
52
+ const sceneType = ctx.sceneType === 'channel' ? 'group' : ctx.sceneType;
53
+ await this.manager.start({
54
+ messageId: ctx.messageId,
55
+ sessionId: ctx.sessionId,
56
+ userId: ctx.userId,
57
+ groupId: ctx.groupId,
58
+ sceneType,
59
+ phase,
60
+ phaseConfig,
61
+ });
62
+ }
63
+
64
+ async stop(ctx: ActivityFeedbackEventContext, phase: ActivityFeedbackPhase): Promise<void> {
65
+ await this.manager.stop({
66
+ sessionId: ctx.sessionId,
67
+ userId: ctx.userId,
68
+ groupId: ctx.groupId,
69
+ phase,
70
+ });
71
+ }
72
+
73
+ async updateThinkingText(ctx: ActivityFeedbackEventContext, text: string): Promise<void> {
74
+ const indicator = this.manager.getActiveIndicator?.('thinking', ctx.options);
75
+ if (indicator?.update) {
76
+ await indicator.update(text);
77
+ }
78
+ }
79
+ }
80
+
81
+ class GenericPhaseDriver implements PhaseDriver {
82
+ private manager?: ActivityFeedbackManager;
83
+
84
+ constructor(
85
+ private readonly endpoint: EndpointWithActivityFeedback,
86
+ private readonly platform: string,
87
+ private readonly adapter: Adapter,
88
+ ) {}
89
+
90
+ private async ensureManager(): Promise<ActivityFeedbackManager> {
91
+ if (this.manager) return this.manager;
92
+ const existing = this.endpoint.$activityFeedback;
93
+ if (existing && isGenericActivityFeedbackManager(existing)) {
94
+ this.manager = existing;
95
+ return existing;
96
+ }
97
+ this.manager = enableActivityFeedbackForBot(this.endpoint, this.platform, this.adapter);
98
+ return this.manager;
99
+ }
100
+
101
+ async start(
102
+ ctx: ActivityFeedbackEventContext,
103
+ phase: ActivityFeedbackPhase,
104
+ phaseConfig: ResolvedActivityFeedbackPhaseConfig,
105
+ ): Promise<void> {
106
+ const manager = await this.ensureManager();
107
+ if (manager.getActiveIndicator(phase, ctx.options)) return;
108
+ await manager.start(phase, ctx.options, phaseConfig);
109
+ }
110
+
111
+ async stop(ctx: ActivityFeedbackEventContext, phase: ActivityFeedbackPhase): Promise<void> {
112
+ const manager = this.endpoint.$activityFeedback;
113
+ if (!manager || !isGenericActivityFeedbackManager(manager)) return;
114
+ await manager.stop(phase, ctx.options);
115
+ }
116
+
117
+ async updateThinkingText(ctx: ActivityFeedbackEventContext, text: string): Promise<void> {
118
+ const manager = this.endpoint.$activityFeedback;
119
+ if (!manager || !isGenericActivityFeedbackManager(manager)) return;
120
+ const indicator = manager.getActiveIndicator('thinking', ctx.options);
121
+ if (indicator?.update) {
122
+ await indicator.update(text);
123
+ }
124
+ }
125
+ }
126
+
127
+ function createPhaseDriver(
128
+ endpoint: EndpointWithActivityFeedback,
129
+ platform: string,
130
+ adapter: Adapter,
131
+ ): PhaseDriver {
132
+ const manager = endpoint.$activityFeedback;
133
+ if (manager && !isGenericActivityFeedbackManager(manager)) {
134
+ return new PlatformPhaseDriver(manager);
135
+ }
136
+ return new GenericPhaseDriver(endpoint, platform, adapter);
137
+ }
138
+
139
+ export class ActivityFeedbackExecutor {
140
+ constructor(private readonly access: ActivityFeedbackEndpointAccess) {}
141
+
142
+ async start(
143
+ ctx: ActivityFeedbackEventContext,
144
+ phase: ActivityFeedbackPhase,
145
+ phaseConfig: ResolvedActivityFeedbackPhaseConfig,
146
+ ): Promise<void> {
147
+ const resolved = this.access.resolve(ctx.platform, ctx.endpointId);
148
+ if (!resolved) return;
149
+ const driver = createPhaseDriver(resolved.endpoint, ctx.platform, resolved.adapter);
150
+ await driver.start(ctx, phase, phaseConfig);
151
+ }
152
+
153
+ async stop(ctx: ActivityFeedbackEventContext, phase: ActivityFeedbackPhase): Promise<void> {
154
+ const resolved = this.access.resolve(ctx.platform, ctx.endpointId);
155
+ if (!resolved?.endpoint.$activityFeedback) return;
156
+ const driver = createPhaseDriver(resolved.endpoint, ctx.platform, resolved.adapter);
157
+ await driver.stop(ctx, phase);
158
+ }
159
+
160
+ async updateThinkingText(ctx: ActivityFeedbackEventContext, text: string): Promise<void> {
161
+ const resolved = this.access.resolve(ctx.platform, ctx.endpointId);
162
+ if (!resolved) return;
163
+ const driver = createPhaseDriver(resolved.endpoint, ctx.platform, resolved.adapter);
164
+ await driver.updateThinkingText(ctx, text);
165
+ }
166
+ }