opc-agent 1.3.0 → 1.3.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.
Files changed (153) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/CONTRIBUTING.md +75 -75
  3. package/README.md +358 -235
  4. package/README.zh-CN.md +415 -415
  5. package/dist/core/dashboard.d.ts +35 -0
  6. package/dist/core/dashboard.js +157 -0
  7. package/dist/core/priority.d.ts +52 -0
  8. package/dist/core/priority.js +102 -0
  9. package/dist/core/streaming.d.ts +56 -0
  10. package/dist/core/streaming.js +160 -0
  11. package/dist/deploy/hermes.js +22 -22
  12. package/dist/deploy/openclaw.js +31 -31
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.js +12 -1
  15. package/dist/templates/code-reviewer.js +5 -5
  16. package/dist/templates/customer-service.js +2 -2
  17. package/dist/templates/data-analyst.js +5 -5
  18. package/dist/templates/knowledge-base.js +2 -2
  19. package/dist/templates/sales-assistant.js +4 -4
  20. package/dist/templates/teacher.js +6 -6
  21. package/dist/tools/gateway.d.ts +28 -0
  22. package/dist/tools/gateway.js +177 -0
  23. package/docs/.vitepress/config.ts +103 -103
  24. package/docs/api/cli.md +48 -48
  25. package/docs/api/oad-schema.md +64 -64
  26. package/docs/api/sdk.md +80 -80
  27. package/docs/guide/concepts.md +51 -51
  28. package/docs/guide/configuration.md +79 -79
  29. package/docs/guide/deployment.md +42 -42
  30. package/docs/guide/getting-started.md +44 -44
  31. package/docs/guide/templates.md +28 -28
  32. package/docs/guide/testing.md +84 -84
  33. package/docs/index.md +27 -27
  34. package/docs/zh/api/cli.md +54 -54
  35. package/docs/zh/api/oad-schema.md +87 -87
  36. package/docs/zh/api/sdk.md +102 -102
  37. package/docs/zh/guide/concepts.md +104 -104
  38. package/docs/zh/guide/configuration.md +135 -135
  39. package/docs/zh/guide/deployment.md +81 -81
  40. package/docs/zh/guide/getting-started.md +82 -82
  41. package/docs/zh/guide/templates.md +84 -84
  42. package/docs/zh/guide/testing.md +88 -88
  43. package/docs/zh/index.md +27 -27
  44. package/examples/customer-service-demo/README.md +90 -90
  45. package/examples/customer-service-demo/oad.yaml +107 -107
  46. package/package.json +50 -50
  47. package/src/analytics/index.ts +66 -66
  48. package/src/channels/discord.ts +192 -192
  49. package/src/channels/email.ts +177 -177
  50. package/src/channels/feishu.ts +236 -236
  51. package/src/channels/index.ts +15 -15
  52. package/src/channels/slack.ts +160 -160
  53. package/src/channels/telegram.ts +90 -90
  54. package/src/channels/voice.ts +106 -106
  55. package/src/channels/webhook.ts +199 -199
  56. package/src/channels/websocket.ts +87 -87
  57. package/src/channels/wechat.ts +149 -149
  58. package/src/cli.ts +1 -119
  59. package/src/core/a2a.ts +143 -143
  60. package/src/core/agent.ts +152 -152
  61. package/src/core/analytics-engine.ts +186 -186
  62. package/src/core/auth.ts +57 -57
  63. package/src/core/cache.ts +141 -141
  64. package/src/core/compose.ts +77 -77
  65. package/src/core/config.ts +14 -14
  66. package/src/core/dashboard.ts +219 -0
  67. package/src/core/errors.ts +148 -148
  68. package/src/core/hitl.ts +138 -138
  69. package/src/core/logger.ts +57 -57
  70. package/src/core/orchestrator.ts +215 -215
  71. package/src/core/performance.ts +187 -187
  72. package/src/core/priority.ts +140 -0
  73. package/src/core/rate-limiter.ts +128 -128
  74. package/src/core/room.ts +109 -109
  75. package/src/core/runtime.ts +152 -152
  76. package/src/core/sandbox.ts +101 -101
  77. package/src/core/security.ts +171 -171
  78. package/src/core/types.ts +68 -68
  79. package/src/core/versioning.ts +106 -106
  80. package/src/core/watch.ts +178 -178
  81. package/src/core/workflow.ts +235 -235
  82. package/src/deploy/hermes.ts +156 -156
  83. package/src/deploy/openclaw.ts +200 -200
  84. package/src/dtv/data.ts +29 -0
  85. package/src/dtv/trust.ts +43 -0
  86. package/src/dtv/value.ts +47 -0
  87. package/src/i18n/index.ts +216 -216
  88. package/src/index.ts +6 -4
  89. package/src/marketplace/index.ts +223 -0
  90. package/src/memory/deepbrain.ts +108 -108
  91. package/src/memory/index.ts +34 -34
  92. package/src/plugins/index.ts +208 -208
  93. package/src/schema/oad.ts +155 -154
  94. package/src/skills/base.ts +16 -16
  95. package/src/skills/document.ts +100 -100
  96. package/src/skills/http.ts +35 -35
  97. package/src/skills/index.ts +27 -27
  98. package/src/skills/scheduler.ts +80 -80
  99. package/src/skills/webhook-trigger.ts +59 -59
  100. package/src/templates/code-reviewer.ts +34 -30
  101. package/src/templates/customer-service.ts +80 -76
  102. package/src/templates/data-analyst.ts +70 -66
  103. package/src/templates/executive-assistant.ts +71 -71
  104. package/src/templates/financial-advisor.ts +60 -60
  105. package/src/templates/knowledge-base.ts +31 -27
  106. package/src/templates/legal-assistant.ts +71 -71
  107. package/src/templates/sales-assistant.ts +79 -75
  108. package/src/templates/teacher.ts +79 -75
  109. package/src/testing/index.ts +181 -181
  110. package/src/tools/calculator.ts +73 -73
  111. package/src/tools/datetime.ts +149 -149
  112. package/src/tools/json-transform.ts +187 -187
  113. package/src/tools/mcp.ts +76 -76
  114. package/src/tools/text-analysis.ts +116 -116
  115. package/templates/Dockerfile +15 -15
  116. package/templates/code-reviewer/README.md +27 -27
  117. package/templates/code-reviewer/oad.yaml +41 -41
  118. package/templates/customer-service/README.md +22 -22
  119. package/templates/customer-service/oad.yaml +36 -36
  120. package/templates/docker-compose.yml +21 -21
  121. package/templates/ecommerce-assistant/README.md +45 -45
  122. package/templates/ecommerce-assistant/oad.yaml +47 -47
  123. package/templates/knowledge-base/README.md +28 -28
  124. package/templates/knowledge-base/oad.yaml +38 -38
  125. package/templates/sales-assistant/README.md +26 -26
  126. package/templates/sales-assistant/oad.yaml +43 -43
  127. package/templates/tech-support/README.md +43 -43
  128. package/templates/tech-support/oad.yaml +45 -45
  129. package/tests/a2a.test.ts +66 -66
  130. package/tests/agent.test.ts +72 -72
  131. package/tests/analytics.test.ts +50 -50
  132. package/tests/channel.test.ts +39 -39
  133. package/tests/e2e.test.ts +134 -134
  134. package/tests/errors.test.ts +83 -83
  135. package/tests/hitl.test.ts +71 -71
  136. package/tests/i18n.test.ts +41 -41
  137. package/tests/mcp.test.ts +54 -54
  138. package/tests/oad.test.ts +68 -68
  139. package/tests/performance.test.ts +115 -115
  140. package/tests/plugin.test.ts +74 -74
  141. package/tests/room.test.ts +106 -106
  142. package/tests/runtime.test.ts +42 -42
  143. package/tests/sandbox.test.ts +46 -46
  144. package/tests/security.test.ts +60 -60
  145. package/tests/templates.test.ts +77 -77
  146. package/tests/v070.test.ts +76 -76
  147. package/tests/versioning.test.ts +75 -75
  148. package/tests/voice.test.ts +61 -61
  149. package/tests/webhook.test.ts +29 -29
  150. package/tests/workflow.test.ts +143 -143
  151. package/tsconfig.json +19 -19
  152. package/vitest.config.ts +9 -9
  153. package/src/traces/index.ts +0 -132
@@ -1,128 +1,128 @@
1
- /**
2
- * Rate Limiter - Per-user and per-provider rate limiting with queuing.
3
- */
4
-
5
- interface RateLimitConfig {
6
- maxRequests: number;
7
- windowMs: number;
8
- }
9
-
10
- interface RateLimitEntry {
11
- timestamps: number[];
12
- queue: Array<{ resolve: () => void; reject: (err: Error) => void }>;
13
- }
14
-
15
- export class RateLimiter {
16
- private userLimits: Map<string, RateLimitEntry> = new Map();
17
- private providerLimits: Map<string, RateLimitEntry> = new Map();
18
- private userConfig: RateLimitConfig;
19
- private providerConfig: RateLimitConfig;
20
- private maxQueueSize: number;
21
-
22
- constructor(opts?: {
23
- userLimit?: RateLimitConfig;
24
- providerLimit?: RateLimitConfig;
25
- maxQueueSize?: number;
26
- }) {
27
- this.userConfig = opts?.userLimit ?? { maxRequests: 60, windowMs: 60_000 };
28
- this.providerConfig = opts?.providerLimit ?? { maxRequests: 100, windowMs: 60_000 };
29
- this.maxQueueSize = opts?.maxQueueSize ?? 50;
30
- }
31
-
32
- /**
33
- * Check if a request is allowed. If not, queues it with backpressure.
34
- * Returns a promise that resolves when the request can proceed.
35
- */
36
- async acquire(userId: string, provider: string): Promise<void> {
37
- await this.checkLimit(userId, this.userLimits, this.userConfig, 'user');
38
- await this.checkLimit(provider, this.providerLimits, this.providerConfig, 'provider');
39
- }
40
-
41
- private async checkLimit(
42
- key: string,
43
- limits: Map<string, RateLimitEntry>,
44
- config: RateLimitConfig,
45
- type: string,
46
- ): Promise<void> {
47
- let entry = limits.get(key);
48
- if (!entry) {
49
- entry = { timestamps: [], queue: [] };
50
- limits.set(key, entry);
51
- }
52
-
53
- const now = Date.now();
54
- // Prune old timestamps
55
- entry.timestamps = entry.timestamps.filter(t => t > now - config.windowMs);
56
-
57
- if (entry.timestamps.length < config.maxRequests) {
58
- entry.timestamps.push(now);
59
- return;
60
- }
61
-
62
- // Rate limited - queue with backpressure
63
- if (entry.queue.length >= this.maxQueueSize) {
64
- throw new Error(`Rate limit exceeded for ${type} "${key}" and queue is full (${this.maxQueueSize})`);
65
- }
66
-
67
- return new Promise<void>((resolve, reject) => {
68
- entry!.queue.push({ resolve, reject });
69
- // Set timeout to process queue when window expires
70
- const oldestTs = entry!.timestamps[0];
71
- const waitMs = oldestTs + config.windowMs - now + 10;
72
- setTimeout(() => {
73
- this.processQueue(key, limits, config);
74
- }, Math.max(waitMs, 100));
75
- });
76
- }
77
-
78
- private processQueue(
79
- key: string,
80
- limits: Map<string, RateLimitEntry>,
81
- config: RateLimitConfig,
82
- ): void {
83
- const entry = limits.get(key);
84
- if (!entry || entry.queue.length === 0) return;
85
-
86
- const now = Date.now();
87
- entry.timestamps = entry.timestamps.filter(t => t > now - config.windowMs);
88
-
89
- while (entry.queue.length > 0 && entry.timestamps.length < config.maxRequests) {
90
- entry.timestamps.push(now);
91
- const item = entry.queue.shift()!;
92
- item.resolve();
93
- }
94
- }
95
-
96
- /**
97
- * Get current usage stats.
98
- */
99
- getUsage(userId?: string, provider?: string): {
100
- user?: { used: number; limit: number; windowMs: number };
101
- provider?: { used: number; limit: number; windowMs: number };
102
- } {
103
- const now = Date.now();
104
- const result: any = {};
105
-
106
- if (userId) {
107
- const entry = this.userLimits.get(userId);
108
- const used = entry ? entry.timestamps.filter(t => t > now - this.userConfig.windowMs).length : 0;
109
- result.user = { used, limit: this.userConfig.maxRequests, windowMs: this.userConfig.windowMs };
110
- }
111
-
112
- if (provider) {
113
- const entry = this.providerLimits.get(provider);
114
- const used = entry ? entry.timestamps.filter(t => t > now - this.providerConfig.windowMs).length : 0;
115
- result.provider = { used, limit: this.providerConfig.maxRequests, windowMs: this.providerConfig.windowMs };
116
- }
117
-
118
- return result;
119
- }
120
-
121
- /**
122
- * Reset all limits.
123
- */
124
- reset(): void {
125
- this.userLimits.clear();
126
- this.providerLimits.clear();
127
- }
128
- }
1
+ /**
2
+ * Rate Limiter - Per-user and per-provider rate limiting with queuing.
3
+ */
4
+
5
+ interface RateLimitConfig {
6
+ maxRequests: number;
7
+ windowMs: number;
8
+ }
9
+
10
+ interface RateLimitEntry {
11
+ timestamps: number[];
12
+ queue: Array<{ resolve: () => void; reject: (err: Error) => void }>;
13
+ }
14
+
15
+ export class RateLimiter {
16
+ private userLimits: Map<string, RateLimitEntry> = new Map();
17
+ private providerLimits: Map<string, RateLimitEntry> = new Map();
18
+ private userConfig: RateLimitConfig;
19
+ private providerConfig: RateLimitConfig;
20
+ private maxQueueSize: number;
21
+
22
+ constructor(opts?: {
23
+ userLimit?: RateLimitConfig;
24
+ providerLimit?: RateLimitConfig;
25
+ maxQueueSize?: number;
26
+ }) {
27
+ this.userConfig = opts?.userLimit ?? { maxRequests: 60, windowMs: 60_000 };
28
+ this.providerConfig = opts?.providerLimit ?? { maxRequests: 100, windowMs: 60_000 };
29
+ this.maxQueueSize = opts?.maxQueueSize ?? 50;
30
+ }
31
+
32
+ /**
33
+ * Check if a request is allowed. If not, queues it with backpressure.
34
+ * Returns a promise that resolves when the request can proceed.
35
+ */
36
+ async acquire(userId: string, provider: string): Promise<void> {
37
+ await this.checkLimit(userId, this.userLimits, this.userConfig, 'user');
38
+ await this.checkLimit(provider, this.providerLimits, this.providerConfig, 'provider');
39
+ }
40
+
41
+ private async checkLimit(
42
+ key: string,
43
+ limits: Map<string, RateLimitEntry>,
44
+ config: RateLimitConfig,
45
+ type: string,
46
+ ): Promise<void> {
47
+ let entry = limits.get(key);
48
+ if (!entry) {
49
+ entry = { timestamps: [], queue: [] };
50
+ limits.set(key, entry);
51
+ }
52
+
53
+ const now = Date.now();
54
+ // Prune old timestamps
55
+ entry.timestamps = entry.timestamps.filter(t => t > now - config.windowMs);
56
+
57
+ if (entry.timestamps.length < config.maxRequests) {
58
+ entry.timestamps.push(now);
59
+ return;
60
+ }
61
+
62
+ // Rate limited - queue with backpressure
63
+ if (entry.queue.length >= this.maxQueueSize) {
64
+ throw new Error(`Rate limit exceeded for ${type} "${key}" and queue is full (${this.maxQueueSize})`);
65
+ }
66
+
67
+ return new Promise<void>((resolve, reject) => {
68
+ entry!.queue.push({ resolve, reject });
69
+ // Set timeout to process queue when window expires
70
+ const oldestTs = entry!.timestamps[0];
71
+ const waitMs = oldestTs + config.windowMs - now + 10;
72
+ setTimeout(() => {
73
+ this.processQueue(key, limits, config);
74
+ }, Math.max(waitMs, 100));
75
+ });
76
+ }
77
+
78
+ private processQueue(
79
+ key: string,
80
+ limits: Map<string, RateLimitEntry>,
81
+ config: RateLimitConfig,
82
+ ): void {
83
+ const entry = limits.get(key);
84
+ if (!entry || entry.queue.length === 0) return;
85
+
86
+ const now = Date.now();
87
+ entry.timestamps = entry.timestamps.filter(t => t > now - config.windowMs);
88
+
89
+ while (entry.queue.length > 0 && entry.timestamps.length < config.maxRequests) {
90
+ entry.timestamps.push(now);
91
+ const item = entry.queue.shift()!;
92
+ item.resolve();
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Get current usage stats.
98
+ */
99
+ getUsage(userId?: string, provider?: string): {
100
+ user?: { used: number; limit: number; windowMs: number };
101
+ provider?: { used: number; limit: number; windowMs: number };
102
+ } {
103
+ const now = Date.now();
104
+ const result: any = {};
105
+
106
+ if (userId) {
107
+ const entry = this.userLimits.get(userId);
108
+ const used = entry ? entry.timestamps.filter(t => t > now - this.userConfig.windowMs).length : 0;
109
+ result.user = { used, limit: this.userConfig.maxRequests, windowMs: this.userConfig.windowMs };
110
+ }
111
+
112
+ if (provider) {
113
+ const entry = this.providerLimits.get(provider);
114
+ const used = entry ? entry.timestamps.filter(t => t > now - this.providerConfig.windowMs).length : 0;
115
+ result.provider = { used, limit: this.providerConfig.maxRequests, windowMs: this.providerConfig.windowMs };
116
+ }
117
+
118
+ return result;
119
+ }
120
+
121
+ /**
122
+ * Reset all limits.
123
+ */
124
+ reset(): void {
125
+ this.userLimits.clear();
126
+ this.providerLimits.clear();
127
+ }
128
+ }
package/src/core/room.ts CHANGED
@@ -1,109 +1,109 @@
1
- import { EventEmitter } from 'events';
2
- import type { Message, IAgent } from './types';
3
-
4
- export interface RoomMessage {
5
- from: string;
6
- to?: string; // undefined = broadcast
7
- topic?: string;
8
- message: Message;
9
- }
10
-
11
- export class Room extends EventEmitter {
12
- readonly name: string;
13
- private agents: Map<string, IAgent> = new Map();
14
- private subscriptions: Map<string, Set<string>> = new Map(); // topic -> agentNames
15
-
16
- constructor(name: string) {
17
- super();
18
- this.name = name;
19
- }
20
-
21
- addAgent(agent: IAgent): void {
22
- this.agents.set(agent.name, agent);
23
- this.emit('agent:join', agent.name);
24
- }
25
-
26
- removeAgent(name: string): void {
27
- this.agents.delete(name);
28
- // Remove from all subscriptions
29
- for (const [, subscribers] of this.subscriptions) {
30
- subscribers.delete(name);
31
- }
32
- this.emit('agent:leave', name);
33
- }
34
-
35
- getAgents(): string[] {
36
- return Array.from(this.agents.keys());
37
- }
38
-
39
- subscribe(agentName: string, topic: string): void {
40
- if (!this.subscriptions.has(topic)) {
41
- this.subscriptions.set(topic, new Set());
42
- }
43
- this.subscriptions.get(topic)!.add(agentName);
44
- }
45
-
46
- unsubscribe(agentName: string, topic: string): void {
47
- this.subscriptions.get(topic)?.delete(agentName);
48
- }
49
-
50
- getSubscribers(topic: string): string[] {
51
- return Array.from(this.subscriptions.get(topic) ?? []);
52
- }
53
-
54
- async send(roomMessage: RoomMessage): Promise<Message[]> {
55
- const responses: Message[] = [];
56
- this.emit('message', roomMessage);
57
-
58
- if (roomMessage.to) {
59
- // Direct message
60
- const agent = this.agents.get(roomMessage.to);
61
- if (agent) {
62
- const response = await agent.handleMessage(roomMessage.message);
63
- responses.push(response);
64
- }
65
- } else if (roomMessage.topic) {
66
- // Topic-based pub/sub
67
- const subscribers = this.subscriptions.get(roomMessage.topic) ?? new Set();
68
- for (const name of subscribers) {
69
- if (name === roomMessage.from) continue;
70
- const agent = this.agents.get(name);
71
- if (agent) {
72
- const response = await agent.handleMessage(roomMessage.message);
73
- responses.push(response);
74
- }
75
- }
76
- } else {
77
- // Broadcast to all except sender
78
- for (const [name, agent] of this.agents) {
79
- if (name === roomMessage.from) continue;
80
- const response = await agent.handleMessage(roomMessage.message);
81
- responses.push(response);
82
- }
83
- }
84
-
85
- return responses;
86
- }
87
-
88
- async broadcast(from: string, content: string): Promise<Message[]> {
89
- const message: Message = {
90
- id: `room_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
91
- role: 'user',
92
- content,
93
- timestamp: Date.now(),
94
- metadata: { room: this.name, from },
95
- };
96
- return this.send({ from, message });
97
- }
98
-
99
- async publishToTopic(from: string, topic: string, content: string): Promise<Message[]> {
100
- const message: Message = {
101
- id: `room_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
102
- role: 'user',
103
- content,
104
- timestamp: Date.now(),
105
- metadata: { room: this.name, from, topic },
106
- };
107
- return this.send({ from, topic, message });
108
- }
109
- }
1
+ import { EventEmitter } from 'events';
2
+ import type { Message, IAgent } from './types';
3
+
4
+ export interface RoomMessage {
5
+ from: string;
6
+ to?: string; // undefined = broadcast
7
+ topic?: string;
8
+ message: Message;
9
+ }
10
+
11
+ export class Room extends EventEmitter {
12
+ readonly name: string;
13
+ private agents: Map<string, IAgent> = new Map();
14
+ private subscriptions: Map<string, Set<string>> = new Map(); // topic -> agentNames
15
+
16
+ constructor(name: string) {
17
+ super();
18
+ this.name = name;
19
+ }
20
+
21
+ addAgent(agent: IAgent): void {
22
+ this.agents.set(agent.name, agent);
23
+ this.emit('agent:join', agent.name);
24
+ }
25
+
26
+ removeAgent(name: string): void {
27
+ this.agents.delete(name);
28
+ // Remove from all subscriptions
29
+ for (const [, subscribers] of this.subscriptions) {
30
+ subscribers.delete(name);
31
+ }
32
+ this.emit('agent:leave', name);
33
+ }
34
+
35
+ getAgents(): string[] {
36
+ return Array.from(this.agents.keys());
37
+ }
38
+
39
+ subscribe(agentName: string, topic: string): void {
40
+ if (!this.subscriptions.has(topic)) {
41
+ this.subscriptions.set(topic, new Set());
42
+ }
43
+ this.subscriptions.get(topic)!.add(agentName);
44
+ }
45
+
46
+ unsubscribe(agentName: string, topic: string): void {
47
+ this.subscriptions.get(topic)?.delete(agentName);
48
+ }
49
+
50
+ getSubscribers(topic: string): string[] {
51
+ return Array.from(this.subscriptions.get(topic) ?? []);
52
+ }
53
+
54
+ async send(roomMessage: RoomMessage): Promise<Message[]> {
55
+ const responses: Message[] = [];
56
+ this.emit('message', roomMessage);
57
+
58
+ if (roomMessage.to) {
59
+ // Direct message
60
+ const agent = this.agents.get(roomMessage.to);
61
+ if (agent) {
62
+ const response = await agent.handleMessage(roomMessage.message);
63
+ responses.push(response);
64
+ }
65
+ } else if (roomMessage.topic) {
66
+ // Topic-based pub/sub
67
+ const subscribers = this.subscriptions.get(roomMessage.topic) ?? new Set();
68
+ for (const name of subscribers) {
69
+ if (name === roomMessage.from) continue;
70
+ const agent = this.agents.get(name);
71
+ if (agent) {
72
+ const response = await agent.handleMessage(roomMessage.message);
73
+ responses.push(response);
74
+ }
75
+ }
76
+ } else {
77
+ // Broadcast to all except sender
78
+ for (const [name, agent] of this.agents) {
79
+ if (name === roomMessage.from) continue;
80
+ const response = await agent.handleMessage(roomMessage.message);
81
+ responses.push(response);
82
+ }
83
+ }
84
+
85
+ return responses;
86
+ }
87
+
88
+ async broadcast(from: string, content: string): Promise<Message[]> {
89
+ const message: Message = {
90
+ id: `room_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
91
+ role: 'user',
92
+ content,
93
+ timestamp: Date.now(),
94
+ metadata: { room: this.name, from },
95
+ };
96
+ return this.send({ from, message });
97
+ }
98
+
99
+ async publishToTopic(from: string, topic: string, content: string): Promise<Message[]> {
100
+ const message: Message = {
101
+ id: `room_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
102
+ role: 'user',
103
+ content,
104
+ timestamp: Date.now(),
105
+ metadata: { room: this.name, from, topic },
106
+ };
107
+ return this.send({ from, topic, message });
108
+ }
109
+ }