@zhin.js/service-activity-feedback 1.0.1 → 1.1.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/src/executor.ts CHANGED
@@ -1,23 +1,185 @@
1
- import type { Adapter } from 'zhin.js';
2
- import { enableActivityFeedbackForBot, isGenericActivityFeedbackManager, type ActivityFeedbackManager, type ActivityFeedbackPhase, type EndpointWithActivityFeedback, type PlatformActivityFeedbackManager, type ResolvedActivityFeedbackPhaseConfig, type ActivityFeedbackEventContext } from '@zhin.js/agent';
1
+ import { enableActivityFeedbackForBot, isGenericActivityFeedbackManager, type ActivityFeedbackManager, type ActivityFeedbackPhase, type ActivityFeedbackSendPort, type EndpointWithActivityFeedback, type PlatformActivityFeedbackManager, type ResolvedActivityFeedbackPhaseConfig, type ActivityFeedbackEventContext } from '@zhin.js/agent';
2
+ import type { OutboundHost } from 'zhin.js';
3
3
 
4
4
  /** IM 侧 endpoint 访问 seam(便于测试注入 fake) */
5
5
  export interface ActivityFeedbackEndpointAccess {
6
6
  resolve(
7
7
  platform: string,
8
- endpointId: string,
9
- ): { endpoint: EndpointWithActivityFeedback; adapter: Adapter } | undefined;
8
+ endpointKey: string,
9
+ ): { endpoint: EndpointWithActivityFeedback; outbound: ActivityFeedbackSendPort } | undefined;
10
10
  }
11
11
 
12
- export function createRootEndpointAccess(root: {
13
- injectAdapter(platform: string): Adapter | undefined;
14
- }): ActivityFeedbackEndpointAccess {
12
+ /** Slice-2: no Adapter inject — executor start/stop no-op when resolve returns undefined. */
13
+ export function createNoopEndpointAccess(): ActivityFeedbackEndpointAccess {
15
14
  return {
16
- resolve(platform, endpointId) {
17
- const adapter = root.injectAdapter(platform);
18
- const endpoint = adapter?.endpoints?.get(endpointId) as EndpointWithActivityFeedback | undefined;
19
- if (!endpoint || !adapter) return undefined;
20
- return { endpoint, adapter };
15
+ resolve() {
16
+ return undefined;
17
+ },
18
+ };
19
+ }
20
+
21
+ function stringifySendContent(content: unknown): string {
22
+ if (typeof content === 'string') return content;
23
+ if (!Array.isArray(content)) return content == null ? '' : String(content);
24
+ return content.map((segment) => {
25
+ if (typeof segment === 'string') return segment;
26
+ if (segment && typeof segment === 'object') {
27
+ const data = (segment as { data?: { text?: string }; text?: string }).data
28
+ ?? (segment as { text?: string });
29
+ if (typeof data === 'object' && data && 'text' in data && typeof data.text === 'string') {
30
+ return data.text;
31
+ }
32
+ if (typeof (segment as { text?: string }).text === 'string') {
33
+ return (segment as { text: string }).text;
34
+ }
35
+ }
36
+ return '';
37
+ }).join('');
38
+ }
39
+
40
+ /**
41
+ * Plugin Runtime: resolve endpoints via OutboundHost → ImRuntime.sendEndpointMessage.
42
+ * Typing/reaction text goes through the unified outbound chain (no legacy Adapter.inject).
43
+ *
44
+ * 按 platform:endpointKey 缓存 { endpoint, outbound }:activity manager 挂在
45
+ * endpoint.$activityFeedback 上,start/stop 必须解析到同一个对象,否则
46
+ * stop 时拿不到 manager,typing 指示器永远无法停止。
47
+ */
48
+ export function createOutboundEndpointAccess(
49
+ outbound: OutboundHost,
50
+ logger?: { debug: (msg: string, ...args: unknown[]) => void },
51
+ ): ActivityFeedbackEndpointAccess {
52
+ const cache = new Map<string, {
53
+ endpoint: EndpointWithActivityFeedback;
54
+ outbound: ActivityFeedbackSendPort;
55
+ }>();
56
+ return {
57
+ resolve(platform, endpointKey) {
58
+ const key = JSON.stringify([platform, endpointKey]);
59
+ const cached = cache.get(key);
60
+ if (cached) return cached;
61
+ const declared = outbound.capabilities?.({ adapter: platform, endpointKey })?.operations;
62
+ const supports = (operation: 'recall' | 'edit' | 'reaction' | 'typing') =>
63
+ declared?.includes(operation) === true;
64
+ const recall = outbound.recall;
65
+ const edit = outbound.edit;
66
+ const addReaction = outbound.addReaction;
67
+ const removeReaction = outbound.removeReaction;
68
+ const typing = outbound.typing;
69
+ const endpoint = {
70
+ $id: endpointKey,
71
+ control: {
72
+ ...(recall && supports('recall') ? {
73
+ recall: async (message: Parameters<typeof recall>[0]['message']) => {
74
+ try {
75
+ await recall({ adapter: platform, endpointKey, message });
76
+ } catch (error) {
77
+ logger?.debug(
78
+ `[ActivityFeedback] outbound recall failed (${key}):`,
79
+ error instanceof Error ? error.message : String(error),
80
+ );
81
+ }
82
+ },
83
+ } : {}),
84
+ ...(edit && supports('edit') ? {
85
+ edit: async (
86
+ message: Parameters<typeof edit>[0]['message'],
87
+ content: unknown,
88
+ ) => {
89
+ try {
90
+ return await edit({ adapter: platform, endpointKey, message, content });
91
+ } catch (error) {
92
+ logger?.debug(
93
+ `[ActivityFeedback] outbound edit failed (${key}):`,
94
+ error instanceof Error ? error.message : String(error),
95
+ );
96
+ return null;
97
+ }
98
+ },
99
+ } : {}),
100
+ ...(addReaction && supports('reaction') ? {
101
+ addReaction: async (
102
+ message: Parameters<typeof addReaction>[0]['message'],
103
+ emoji: string,
104
+ hint?: { sceneType?: 'private' | 'group' | 'channel'; channelId?: string },
105
+ ) => {
106
+ try {
107
+ return await addReaction({
108
+ adapter: platform,
109
+ endpointKey,
110
+ message,
111
+ emoji,
112
+ sceneType: hint?.sceneType,
113
+ channelId: hint?.channelId,
114
+ });
115
+ } catch (error) {
116
+ logger?.debug(
117
+ `[ActivityFeedback] outbound addReaction failed (${key}):`,
118
+ error instanceof Error ? error.message : String(error),
119
+ );
120
+ return null;
121
+ }
122
+ },
123
+ } : {}),
124
+ ...(removeReaction && supports('reaction') ? {
125
+ removeReaction: async (
126
+ message: Parameters<typeof removeReaction>[0]['message'],
127
+ reactionId: string,
128
+ ) => {
129
+ try {
130
+ await removeReaction({ adapter: platform, endpointKey, message, reactionId });
131
+ } catch (error) {
132
+ logger?.debug(
133
+ `[ActivityFeedback] outbound removeReaction failed (${key}):`,
134
+ error instanceof Error ? error.message : String(error),
135
+ );
136
+ }
137
+ },
138
+ } : {}),
139
+ ...(typing && supports('typing') ? {
140
+ typing: async (
141
+ conversation: Parameters<typeof typing>[0]['conversation'],
142
+ active?: boolean,
143
+ ) => {
144
+ try {
145
+ await typing({ adapter: platform, endpointKey, conversation, active });
146
+ } catch (error) {
147
+ logger?.debug(
148
+ `[ActivityFeedback] outbound typing failed (${key}):`,
149
+ error instanceof Error ? error.message : String(error),
150
+ );
151
+ }
152
+ },
153
+ } : {}),
154
+ },
155
+ } as EndpointWithActivityFeedback;
156
+ const sendPort: ActivityFeedbackSendPort = {
157
+ send: async ({ conversation, content }) => {
158
+ const text = stringifySendContent(content);
159
+ if (!text || !conversation.id) return null;
160
+ try {
161
+ const messageId = await outbound.send({
162
+ adapter: platform,
163
+ endpointKey,
164
+ conversation: {
165
+ kind: conversation.kind,
166
+ id: conversation.id,
167
+ },
168
+ content: text,
169
+ });
170
+ return messageId || null;
171
+ } catch (error) {
172
+ logger?.debug(
173
+ `[ActivityFeedback] outbound send failed (${key}):`,
174
+ error instanceof Error ? error.message : String(error),
175
+ );
176
+ return null;
177
+ }
178
+ },
179
+ };
180
+ const resolved = { endpoint, outbound: sendPort };
181
+ cache.set(key, resolved);
182
+ return resolved;
21
183
  },
22
184
  };
23
185
  }
@@ -75,7 +237,7 @@ class GenericPhaseDriver implements PhaseDriver {
75
237
  constructor(
76
238
  private readonly endpoint: EndpointWithActivityFeedback,
77
239
  private readonly platform: string,
78
- private readonly adapter: Adapter,
240
+ private readonly outbound: ActivityFeedbackSendPort,
79
241
  ) {}
80
242
 
81
243
  private async ensureManager(): Promise<ActivityFeedbackManager> {
@@ -85,7 +247,7 @@ class GenericPhaseDriver implements PhaseDriver {
85
247
  this.manager = existing;
86
248
  return existing;
87
249
  }
88
- this.manager = enableActivityFeedbackForBot(this.endpoint, this.platform, this.adapter);
250
+ this.manager = enableActivityFeedbackForBot(this.endpoint, this.platform, this.outbound);
89
251
  return this.manager;
90
252
  }
91
253
 
@@ -118,13 +280,13 @@ class GenericPhaseDriver implements PhaseDriver {
118
280
  function createPhaseDriver(
119
281
  endpoint: EndpointWithActivityFeedback,
120
282
  platform: string,
121
- adapter: Adapter,
283
+ outbound: ActivityFeedbackSendPort,
122
284
  ): PhaseDriver {
123
285
  const manager = endpoint.$activityFeedback;
124
286
  if (manager && !isGenericActivityFeedbackManager(manager)) {
125
287
  return new PlatformPhaseDriver(manager);
126
288
  }
127
- return new GenericPhaseDriver(endpoint, platform, adapter);
289
+ return new GenericPhaseDriver(endpoint, platform, outbound);
128
290
  }
129
291
 
130
292
  export class ActivityFeedbackExecutor {
@@ -135,23 +297,37 @@ export class ActivityFeedbackExecutor {
135
297
  phase: ActivityFeedbackPhase,
136
298
  phaseConfig: ResolvedActivityFeedbackPhaseConfig,
137
299
  ): Promise<void> {
138
- const resolved = this.access.resolve(ctx.platform, ctx.endpointId);
300
+ const resolved = this.access.resolve(ctx.platform, ctx.endpointKey);
139
301
  if (!resolved) return;
140
- const driver = createPhaseDriver(resolved.endpoint, ctx.platform, resolved.adapter);
302
+ const driver = createPhaseDriver(resolved.endpoint, ctx.platform, resolved.outbound);
141
303
  await driver.start(ctx, phase, phaseConfig);
142
304
  }
143
305
 
144
306
  async stop(ctx: ActivityFeedbackEventContext, phase: ActivityFeedbackPhase): Promise<void> {
145
- const resolved = this.access.resolve(ctx.platform, ctx.endpointId);
307
+ const resolved = this.access.resolve(ctx.platform, ctx.endpointKey);
146
308
  if (!resolved?.endpoint.$activityFeedback) return;
147
- const driver = createPhaseDriver(resolved.endpoint, ctx.platform, resolved.adapter);
309
+ const driver = createPhaseDriver(resolved.endpoint, ctx.platform, resolved.outbound);
148
310
  await driver.stop(ctx, phase);
149
311
  }
150
312
 
151
- async updateThinkingText(ctx: ActivityFeedbackEventContext, text: string): Promise<void> {
152
- const resolved = this.access.resolve(ctx.platform, ctx.endpointId);
313
+ async updateText(
314
+ ctx: ActivityFeedbackEventContext,
315
+ phase: ActivityFeedbackPhase,
316
+ text: string,
317
+ ): Promise<void> {
318
+ const resolved = this.access.resolve(ctx.platform, ctx.endpointKey);
153
319
  if (!resolved) return;
154
- const driver = createPhaseDriver(resolved.endpoint, ctx.platform, resolved.adapter);
155
- await driver.updateThinkingText(ctx, text);
320
+ const driver = createPhaseDriver(resolved.endpoint, ctx.platform, resolved.outbound);
321
+ const manager = resolved.endpoint.$activityFeedback;
322
+ if (!manager || !isGenericActivityFeedbackManager(manager)) {
323
+ if (phase === 'thinking') await driver.updateThinkingText(ctx, text);
324
+ return;
325
+ }
326
+ const indicator = manager.getActiveIndicator(phase, ctx.options);
327
+ if (indicator?.update) await indicator.update(text);
328
+ }
329
+
330
+ async updateThinkingText(ctx: ActivityFeedbackEventContext, text: string): Promise<void> {
331
+ await this.updateText(ctx, 'thinking', text);
156
332
  }
157
333
  }
package/src/index.ts CHANGED
@@ -1,34 +1,13 @@
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.debug('[ActivityFeedback] disabled by activityFeedback.enabled=false');
25
- }
26
-
27
6
  export {
28
- bindActivityFeedbackToAIEvents,
7
+ bindActivityFeedbackToAIEventBus,
8
+ createActivityFeedbackAIEventHandlers,
29
9
  createActivityFeedbackOrchestrator,
30
- createActivityFeedbackOrchestratorFromPlugin,
31
- mountActivityFeedbackService,
10
+ createActivityFeedbackOrchestratorForRuntime,
32
11
  } from './ai-event-binder.js';
33
12
  export { ActivityFeedbackOrchestrator } from './orchestrator.js';
34
13
  export { ActivityFeedbackPolicy } from './policy.js';
@@ -38,4 +17,7 @@ export {
38
17
  type ActivityFeedbackServiceConfig,
39
18
  } from './config.js';
40
19
  export type { ActivityFeedbackEndpointAccess } from './executor.js';
41
- export default plugin;
20
+ export {
21
+ createNoopEndpointAccess,
22
+ createOutboundEndpointAccess,
23
+ } from './executor.js';
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  toActivityFeedbackEventContext,
3
3
  isActivityFeedbackEnabled,
4
+ applySubagentActivityPrefixToConfig,
5
+ withSubagentActivityPrefix,
4
6
  type AIEventPayload,
5
7
  type ActivityFeedbackPhase,
6
8
  } from '@zhin.js/agent';
@@ -10,6 +12,17 @@ import { ActivityFeedbackPolicy } from './policy.js';
10
12
  type Logger = { debug: (msg: string, ...args: unknown[]) => void; error: (msg: string, ...args: unknown[]) => void };
11
13
 
12
14
  export class ActivityFeedbackOrchestrator {
15
+ private readonly active = new Map<string, {
16
+ ctx: NonNullable<ReturnType<typeof toActivityFeedbackEventContext>>;
17
+ phase: ActivityFeedbackPhase;
18
+ }>();
19
+ private readonly transient = new Map<string, {
20
+ timer: ReturnType<typeof setTimeout>;
21
+ ctx: NonNullable<ReturnType<typeof toActivityFeedbackEventContext>>;
22
+ phase: ActivityFeedbackPhase;
23
+ }>();
24
+ private readonly pendingCleanup = new Set<Promise<void>>();
25
+
13
26
  constructor(
14
27
  private readonly policy: ActivityFeedbackPolicy,
15
28
  private readonly executor: ActivityFeedbackExecutor,
@@ -18,16 +31,36 @@ export class ActivityFeedbackOrchestrator {
18
31
 
19
32
  async startPhase(payload: AIEventPayload, phase: ActivityFeedbackPhase, reason: string): Promise<void> {
20
33
  const gatePhase = phase as import('@zhin.js/agent').ActivityFeedbackGatePhase;
21
- if (!isActivityFeedbackEnabled(payload, gatePhase)) return;
34
+ if (!isActivityFeedbackEnabled(payload, gatePhase)) {
35
+ this.log.debug(
36
+ `[ActivityFeedback] skip ${phase} (${reason}): gate closed`
37
+ + ` (eligible=${String(payload.hookContext?.activityFeedbackEligible)})`,
38
+ );
39
+ return;
40
+ }
22
41
  const ctx = toActivityFeedbackEventContext(payload);
23
- if (!ctx) return;
42
+ if (!ctx) {
43
+ this.log.debug(
44
+ `[ActivityFeedback] skip ${phase} (${reason}): unresolvable context`
45
+ + ` (platform=${String(payload.platform)} endpoint=${String(payload.endpointKey)})`,
46
+ );
47
+ return;
48
+ }
24
49
 
25
50
  try {
26
- const resolution = this.policy.resolvePhase(ctx.platform, ctx.endpointId, phase, ctx.sceneType);
27
- if (resolution.kind !== 'active') return;
51
+ const resolution = this.policy.resolvePhase(ctx.platform, ctx.endpointKey, phase, ctx.sceneType);
52
+ if (resolution.kind !== 'active') {
53
+ this.log.debug(
54
+ `[ActivityFeedback] skip ${phase} (${reason}): policy=${resolution.kind}`
55
+ + ` (${ctx.platform}:${ctx.endpointKey} ${ctx.sceneType})`,
56
+ );
57
+ return;
58
+ }
28
59
 
60
+ const config = applySubagentActivityPrefixToConfig(resolution.config, payload);
29
61
  this.log.debug(`[ActivityFeedback] start ${phase} (${reason}) session=${ctx.sessionId}`);
30
- await this.executor.start(ctx, phase, resolution.config);
62
+ await this.executor.start(ctx, phase, config);
63
+ this.active.set(this.phaseKey(ctx, phase), { ctx, phase });
31
64
  } catch (error) {
32
65
  this.log.error(`[ActivityFeedback] start ${phase} failed (${reason}):`, error);
33
66
  }
@@ -36,6 +69,9 @@ export class ActivityFeedbackOrchestrator {
36
69
  async stopPhase(payload: AIEventPayload, phase: ActivityFeedbackPhase, reason: string): Promise<void> {
37
70
  const ctx = toActivityFeedbackEventContext(payload);
38
71
  if (!ctx) return;
72
+ const key = this.phaseKey(ctx, phase);
73
+ this.clearTransient(key);
74
+ this.active.delete(key);
39
75
 
40
76
  try {
41
77
  this.log.debug(`[ActivityFeedback] stop ${phase} (${reason}) session=${ctx.sessionId}`);
@@ -46,13 +82,76 @@ export class ActivityFeedbackOrchestrator {
46
82
  }
47
83
 
48
84
  async updateThinkingText(payload: AIEventPayload, text: string): Promise<void> {
85
+ await this.updatePhaseText(payload, 'thinking', text);
86
+ }
87
+
88
+ async updatePhaseText(
89
+ payload: AIEventPayload,
90
+ phase: ActivityFeedbackPhase,
91
+ text: string,
92
+ ): Promise<void> {
49
93
  const ctx = toActivityFeedbackEventContext(payload);
50
94
  if (!ctx || !text) return;
51
95
 
52
96
  try {
53
- await this.executor.updateThinkingText(ctx, text);
97
+ await this.executor.updateText(ctx, phase, withSubagentActivityPrefix(text, payload));
54
98
  } catch (error) {
55
- this.log.error('[ActivityFeedback] thinking update failed:', error);
99
+ this.log.error(`[ActivityFeedback] ${phase} update failed:`, error);
56
100
  }
57
101
  }
102
+
103
+ async showTransientPhase(
104
+ payload: AIEventPayload,
105
+ phase: ActivityFeedbackPhase,
106
+ reason: string,
107
+ ): Promise<void> {
108
+ await this.startPhase(payload, phase, reason);
109
+ const ctx = toActivityFeedbackEventContext(payload);
110
+ if (!ctx) return;
111
+ const resolution = this.policy.resolvePhase(ctx.platform, ctx.endpointKey, phase, ctx.sceneType);
112
+ if (resolution.kind !== 'active') return;
113
+ const delay = resolution.config.removeDelay ?? 3_000;
114
+ const key = this.phaseKey(ctx, phase);
115
+ this.clearTransient(key);
116
+ const timer = setTimeout(() => {
117
+ this.transient.delete(key);
118
+ this.active.delete(key);
119
+ const cleanup = this.executor.stop(ctx, phase)
120
+ .catch((error) => {
121
+ this.log.error(`[ActivityFeedback] transient ${phase} cleanup failed:`, error);
122
+ })
123
+ .finally(() => {
124
+ this.pendingCleanup.delete(cleanup);
125
+ });
126
+ this.pendingCleanup.add(cleanup);
127
+ }, Math.max(0, delay));
128
+ timer.unref?.();
129
+ this.transient.set(key, { timer, ctx, phase });
130
+ }
131
+
132
+ async dispose(): Promise<void> {
133
+ const pending = [...this.active.values()];
134
+ const timers = [...this.transient.values()];
135
+ this.active.clear();
136
+ this.transient.clear();
137
+ for (const item of timers) clearTimeout(item.timer);
138
+ await Promise.allSettled([
139
+ ...pending.map((item) => this.executor.stop(item.ctx, item.phase)),
140
+ ...this.pendingCleanup,
141
+ ]);
142
+ }
143
+
144
+ private phaseKey(
145
+ ctx: NonNullable<ReturnType<typeof toActivityFeedbackEventContext>>,
146
+ phase: ActivityFeedbackPhase,
147
+ ): string {
148
+ return JSON.stringify([ctx.platform, ctx.endpointKey, ctx.sessionId, phase, ctx.messageId]);
149
+ }
150
+
151
+ private clearTransient(key: string): void {
152
+ const current = this.transient.get(key);
153
+ if (!current) return;
154
+ clearTimeout(current.timer);
155
+ this.transient.delete(key);
156
+ }
58
157
  }
package/src/policy.ts CHANGED
@@ -23,7 +23,7 @@ export class ActivityFeedbackPolicy {
23
23
 
24
24
  resolvePhase(
25
25
  platform: string,
26
- endpointId: string,
26
+ endpointKey: string,
27
27
  phase: ActivityFeedbackPhase,
28
28
  sceneType: ActivitySceneType,
29
29
  ): PhaseResolution {
@@ -34,7 +34,8 @@ export class ActivityFeedbackPolicy {
34
34
  const policy = mergeActivityFeedbackLayers(
35
35
  this.service.defaults,
36
36
  this.service.platforms?.[platform],
37
- this.service.endpoints?.[`${platform}:${endpointId}`],
37
+ this.service.endpoints?.[`${platform}:${endpointKey}`],
38
+ schedulePhaseLayer(this.service, phase),
38
39
  );
39
40
 
40
41
  if (policy?.enabled === false) {
@@ -49,3 +50,18 @@ export class ActivityFeedbackPolicy {
49
50
  return { kind: 'active', config };
50
51
  }
51
52
  }
53
+
54
+ function schedulePhaseLayer(
55
+ service: ActivityFeedbackServiceConfig,
56
+ phase: ActivityFeedbackPhase,
57
+ ): import('@zhin.js/agent').ActivityFeedbackConfig | undefined {
58
+ const scheduleKey = phase === 'schedule_start'
59
+ ? 'start'
60
+ : phase === 'schedule_finish'
61
+ ? 'finish'
62
+ : phase === 'schedule_error'
63
+ ? 'error'
64
+ : undefined;
65
+ const scenes = scheduleKey ? service.schedule?.phases?.[scheduleKey] : undefined;
66
+ return scenes ? { phases: { [phase]: scenes } } : undefined;
67
+ }
package/plugin.yml DELETED
@@ -1,2 +0,0 @@
1
- name: service-activity-feedback
2
- description: Activity Feedback 服务 — 订阅 AI 事件,按 endpoint 配置驱动 queued/active/thinking 三阶段反馈