opc-agent 3.0.0 → 4.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.
Files changed (151) hide show
  1. package/README.md +30 -24
  2. package/dist/channels/dingtalk.d.ts +17 -0
  3. package/dist/channels/dingtalk.js +38 -0
  4. package/dist/channels/googlechat.d.ts +14 -0
  5. package/dist/channels/googlechat.js +37 -0
  6. package/dist/channels/imessage.d.ts +13 -0
  7. package/dist/channels/imessage.js +28 -0
  8. package/dist/channels/irc.d.ts +20 -0
  9. package/dist/channels/irc.js +71 -0
  10. package/dist/channels/line.d.ts +14 -0
  11. package/dist/channels/line.js +28 -0
  12. package/dist/channels/matrix.d.ts +15 -0
  13. package/dist/channels/matrix.js +28 -0
  14. package/dist/channels/mattermost.d.ts +18 -0
  15. package/dist/channels/mattermost.js +49 -0
  16. package/dist/channels/msteams.d.ts +14 -0
  17. package/dist/channels/msteams.js +28 -0
  18. package/dist/channels/nostr.d.ts +14 -0
  19. package/dist/channels/nostr.js +28 -0
  20. package/dist/channels/qq.d.ts +15 -0
  21. package/dist/channels/qq.js +28 -0
  22. package/dist/channels/signal.d.ts +14 -0
  23. package/dist/channels/signal.js +28 -0
  24. package/dist/channels/sms.d.ts +15 -0
  25. package/dist/channels/sms.js +28 -0
  26. package/dist/channels/twitch.d.ts +17 -0
  27. package/dist/channels/twitch.js +59 -0
  28. package/dist/channels/voice-call.d.ts +27 -0
  29. package/dist/channels/voice-call.js +82 -0
  30. package/dist/channels/whatsapp.d.ts +14 -0
  31. package/dist/channels/whatsapp.js +28 -0
  32. package/dist/cli.js +36 -0
  33. package/dist/core/api-server.d.ts +25 -0
  34. package/dist/core/api-server.js +286 -0
  35. package/dist/core/audio.d.ts +50 -0
  36. package/dist/core/audio.js +68 -0
  37. package/dist/core/context-discovery.d.ts +16 -0
  38. package/dist/core/context-discovery.js +107 -0
  39. package/dist/core/context-refs.d.ts +29 -0
  40. package/dist/core/context-refs.js +162 -0
  41. package/dist/core/gateway.d.ts +53 -0
  42. package/dist/core/gateway.js +80 -0
  43. package/dist/core/heartbeat.d.ts +19 -0
  44. package/dist/core/heartbeat.js +50 -0
  45. package/dist/core/hooks.d.ts +28 -0
  46. package/dist/core/hooks.js +82 -0
  47. package/dist/core/ide-bridge.d.ts +53 -0
  48. package/dist/core/ide-bridge.js +97 -0
  49. package/dist/core/node-network.d.ts +23 -0
  50. package/dist/core/node-network.js +77 -0
  51. package/dist/core/profiles.d.ts +27 -0
  52. package/dist/core/profiles.js +131 -0
  53. package/dist/core/sandbox.d.ts +25 -0
  54. package/dist/core/sandbox.js +84 -1
  55. package/dist/core/session-manager.d.ts +33 -0
  56. package/dist/core/session-manager.js +157 -0
  57. package/dist/core/vision.d.ts +45 -0
  58. package/dist/core/vision.js +177 -0
  59. package/dist/index.d.ts +64 -1
  60. package/dist/index.js +86 -3
  61. package/dist/memory/context-compressor.d.ts +43 -0
  62. package/dist/memory/context-compressor.js +167 -0
  63. package/dist/memory/index.d.ts +4 -0
  64. package/dist/memory/index.js +5 -1
  65. package/dist/memory/user-profiler.d.ts +50 -0
  66. package/dist/memory/user-profiler.js +201 -0
  67. package/dist/schema/oad.d.ts +12 -12
  68. package/dist/security/approvals.d.ts +53 -0
  69. package/dist/security/approvals.js +115 -0
  70. package/dist/security/elevated.d.ts +41 -0
  71. package/dist/security/elevated.js +89 -0
  72. package/dist/security/index.d.ts +6 -0
  73. package/dist/security/index.js +7 -1
  74. package/dist/security/secrets.d.ts +34 -0
  75. package/dist/security/secrets.js +115 -0
  76. package/dist/tools/builtin/browser.d.ts +47 -0
  77. package/dist/tools/builtin/browser.js +284 -0
  78. package/dist/tools/builtin/home-assistant.d.ts +12 -0
  79. package/dist/tools/builtin/home-assistant.js +126 -0
  80. package/dist/tools/builtin/index.d.ts +6 -1
  81. package/dist/tools/builtin/index.js +18 -2
  82. package/dist/tools/builtin/rl-tools.d.ts +13 -0
  83. package/dist/tools/builtin/rl-tools.js +228 -0
  84. package/dist/tools/builtin/vision.d.ts +6 -0
  85. package/dist/tools/builtin/vision.js +61 -0
  86. package/package.json +3 -3
  87. package/src/channels/dingtalk.ts +46 -0
  88. package/src/channels/googlechat.ts +42 -0
  89. package/src/channels/imessage.ts +32 -0
  90. package/src/channels/irc.ts +82 -0
  91. package/src/channels/line.ts +33 -0
  92. package/src/channels/matrix.ts +34 -0
  93. package/src/channels/mattermost.ts +57 -0
  94. package/src/channels/msteams.ts +33 -0
  95. package/src/channels/nostr.ts +33 -0
  96. package/src/channels/qq.ts +34 -0
  97. package/src/channels/signal.ts +33 -0
  98. package/src/channels/sms.ts +34 -0
  99. package/src/channels/twitch.ts +65 -0
  100. package/src/channels/voice-call.ts +100 -0
  101. package/src/channels/whatsapp.ts +33 -0
  102. package/src/cli.ts +40 -0
  103. package/src/core/api-server.ts +277 -0
  104. package/src/core/audio.ts +98 -0
  105. package/src/core/context-discovery.ts +85 -0
  106. package/src/core/context-refs.ts +140 -0
  107. package/src/core/gateway.ts +106 -0
  108. package/src/core/heartbeat.ts +51 -0
  109. package/src/core/hooks.ts +105 -0
  110. package/src/core/ide-bridge.ts +133 -0
  111. package/src/core/node-network.ts +86 -0
  112. package/src/core/profiles.ts +122 -0
  113. package/src/core/sandbox.ts +100 -0
  114. package/src/core/session-manager.ts +137 -0
  115. package/src/core/vision.ts +180 -0
  116. package/src/index.ts +84 -1
  117. package/src/memory/context-compressor.ts +189 -0
  118. package/src/memory/index.ts +4 -0
  119. package/src/memory/user-profiler.ts +215 -0
  120. package/src/security/approvals.ts +143 -0
  121. package/src/security/elevated.ts +105 -0
  122. package/src/security/index.ts +6 -0
  123. package/src/security/secrets.ts +129 -0
  124. package/src/tools/builtin/browser.ts +299 -0
  125. package/src/tools/builtin/home-assistant.ts +116 -0
  126. package/src/tools/builtin/index.ts +9 -2
  127. package/src/tools/builtin/rl-tools.ts +243 -0
  128. package/src/tools/builtin/vision.ts +64 -0
  129. package/tests/api-server.test.ts +148 -0
  130. package/tests/approvals.test.ts +89 -0
  131. package/tests/audio.test.ts +40 -0
  132. package/tests/browser.test.ts +179 -0
  133. package/tests/builtin-tools.test.ts +83 -83
  134. package/tests/channels-extra.test.ts +45 -0
  135. package/tests/context-compressor.test.ts +172 -0
  136. package/tests/context-refs.test.ts +121 -0
  137. package/tests/elevated.test.ts +69 -0
  138. package/tests/gateway.test.ts +63 -71
  139. package/tests/home-assistant.test.ts +40 -0
  140. package/tests/hooks.test.ts +79 -0
  141. package/tests/ide-bridge.test.ts +38 -0
  142. package/tests/node-network.test.ts +74 -0
  143. package/tests/profiles.test.ts +61 -0
  144. package/tests/rl-tools.test.ts +93 -0
  145. package/tests/sandbox-manager.test.ts +46 -0
  146. package/tests/secrets.test.ts +107 -0
  147. package/tests/tools/builtin-extended.test.ts +138 -138
  148. package/tests/user-profiler.test.ts +169 -0
  149. package/tests/v090-features.test.ts +254 -0
  150. package/tests/vision.test.ts +61 -0
  151. package/tests/voice-call.test.ts +47 -0
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rlTools = exports.rlResetEpisode = exports.rlGetStatistics = exports.rlUpdatePolicy = exports.rlExplorationSuggest = exports.rlRewardSignal = exports.rlGenerateTrainingData = exports.rlCompareStrategies = exports.rlGetBestStrategy = exports.rlEvaluateOutcome = exports.rlRecordTrajectory = void 0;
4
+ const trajectories = [];
5
+ const policies = new Map();
6
+ let currentEpisode = null;
7
+ function getOrCreateEpisode(taskType) {
8
+ if (!currentEpisode || currentEpisode.taskType !== taskType) {
9
+ currentEpisode = {
10
+ id: `ep_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
11
+ taskType,
12
+ actions: [],
13
+ totalReward: 0,
14
+ };
15
+ trajectories.push(currentEpisode);
16
+ }
17
+ return currentEpisode;
18
+ }
19
+ exports.rlRecordTrajectory = {
20
+ name: 'rl_record_trajectory',
21
+ description: 'Record action sequences and outcomes for RL training',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ taskType: { type: 'string' },
26
+ action: { type: 'string' },
27
+ outcome: { type: 'string', enum: ['success', 'partial', 'failure'] },
28
+ },
29
+ required: ['taskType', 'action'],
30
+ },
31
+ async execute(input) {
32
+ const ep = getOrCreateEpisode(input.taskType);
33
+ ep.actions.push({ action: input.action, timestamp: Date.now() });
34
+ if (input.outcome)
35
+ ep.outcome = input.outcome;
36
+ return { content: JSON.stringify({ episodeId: ep.id, actionsRecorded: ep.actions.length }) };
37
+ },
38
+ };
39
+ exports.rlEvaluateOutcome = {
40
+ name: 'rl_evaluate_outcome',
41
+ description: "Score an action's outcome (success/partial/failure)",
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: {
45
+ episodeId: { type: 'string' },
46
+ outcome: { type: 'string', enum: ['success', 'partial', 'failure'] },
47
+ },
48
+ required: ['outcome'],
49
+ },
50
+ async execute(input) {
51
+ const ep = input.episodeId
52
+ ? trajectories.find(t => t.id === input.episodeId)
53
+ : currentEpisode;
54
+ if (!ep)
55
+ return { content: 'No active episode found', isError: true };
56
+ ep.outcome = input.outcome;
57
+ const score = ep.outcome === 'success' ? 1 : ep.outcome === 'partial' ? 0.5 : 0;
58
+ return { content: JSON.stringify({ episodeId: ep.id, outcome: ep.outcome, score }) };
59
+ },
60
+ };
61
+ exports.rlGetBestStrategy = {
62
+ name: 'rl_get_best_strategy',
63
+ description: 'Retrieve best-performing strategy for a task type',
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: { taskType: { type: 'string' } },
67
+ required: ['taskType'],
68
+ },
69
+ async execute(input) {
70
+ const taskType = input.taskType;
71
+ const relevant = trajectories.filter(t => t.taskType === taskType && t.outcome === 'success');
72
+ if (relevant.length === 0)
73
+ return { content: JSON.stringify({ strategy: null, message: 'No successful strategies found' }) };
74
+ const best = relevant.reduce((a, b) => a.totalReward >= b.totalReward ? a : b);
75
+ return { content: JSON.stringify({ strategy: best.actions.map(a => a.action), totalReward: best.totalReward }) };
76
+ },
77
+ };
78
+ exports.rlCompareStrategies = {
79
+ name: 'rl_compare_strategies',
80
+ description: 'Compare multiple strategies by success rate',
81
+ inputSchema: {
82
+ type: 'object',
83
+ properties: { taskType: { type: 'string' } },
84
+ required: ['taskType'],
85
+ },
86
+ async execute(input) {
87
+ const taskType = input.taskType;
88
+ const relevant = trajectories.filter(t => t.taskType === taskType && t.outcome);
89
+ const total = relevant.length;
90
+ const successes = relevant.filter(t => t.outcome === 'success').length;
91
+ const partials = relevant.filter(t => t.outcome === 'partial').length;
92
+ const failures = relevant.filter(t => t.outcome === 'failure').length;
93
+ return {
94
+ content: JSON.stringify({
95
+ taskType, total, successRate: total ? successes / total : 0,
96
+ breakdown: { successes, partials, failures },
97
+ }),
98
+ };
99
+ },
100
+ };
101
+ exports.rlGenerateTrainingData = {
102
+ name: 'rl_generate_training_data',
103
+ description: 'Export trajectories as fine-tuning JSONL',
104
+ inputSchema: {
105
+ type: 'object',
106
+ properties: { taskType: { type: 'string' }, minReward: { type: 'number' } },
107
+ },
108
+ async execute(input) {
109
+ let data = trajectories;
110
+ if (input.taskType)
111
+ data = data.filter(t => t.taskType === input.taskType);
112
+ if (input.minReward != null)
113
+ data = data.filter(t => t.totalReward >= input.minReward);
114
+ const jsonl = data.map(t => JSON.stringify({
115
+ messages: [
116
+ { role: 'system', content: `Task: ${t.taskType}` },
117
+ ...t.actions.map(a => ({ role: 'assistant', content: a.action })),
118
+ ],
119
+ outcome: t.outcome,
120
+ reward: t.totalReward,
121
+ })).join('\n');
122
+ return { content: jsonl || '(no data)' };
123
+ },
124
+ };
125
+ exports.rlRewardSignal = {
126
+ name: 'rl_reward_signal',
127
+ description: 'Record positive/negative reward for last action',
128
+ inputSchema: {
129
+ type: 'object',
130
+ properties: { reward: { type: 'number' }, reason: { type: 'string' } },
131
+ required: ['reward'],
132
+ },
133
+ async execute(input) {
134
+ if (!currentEpisode || currentEpisode.actions.length === 0) {
135
+ return { content: 'No current episode or actions to reward', isError: true };
136
+ }
137
+ const lastAction = currentEpisode.actions[currentEpisode.actions.length - 1];
138
+ lastAction.reward = input.reward;
139
+ currentEpisode.totalReward += input.reward;
140
+ return { content: JSON.stringify({ action: lastAction.action, reward: input.reward, totalReward: currentEpisode.totalReward }) };
141
+ },
142
+ };
143
+ exports.rlExplorationSuggest = {
144
+ name: 'rl_exploration_suggest',
145
+ description: 'Suggest alternative approaches (exploration)',
146
+ inputSchema: {
147
+ type: 'object',
148
+ properties: { taskType: { type: 'string' }, currentAction: { type: 'string' } },
149
+ required: ['taskType'],
150
+ },
151
+ async execute(input) {
152
+ const taskType = input.taskType;
153
+ const allActions = new Set();
154
+ trajectories.filter(t => t.taskType === taskType).forEach(t => t.actions.forEach(a => allActions.add(a.action)));
155
+ const suggestions = Array.from(allActions).filter(a => a !== input.currentAction).slice(0, 5);
156
+ if (suggestions.length === 0) {
157
+ return { content: JSON.stringify({ suggestions: [], message: 'No alternative actions found. Try a completely new approach.' }) };
158
+ }
159
+ return { content: JSON.stringify({ suggestions }) };
160
+ },
161
+ };
162
+ exports.rlUpdatePolicy = {
163
+ name: 'rl_update_policy',
164
+ description: "Update agent's action preferences based on rewards",
165
+ inputSchema: {
166
+ type: 'object',
167
+ properties: { taskType: { type: 'string' }, action: { type: 'string' }, weight: { type: 'number' } },
168
+ required: ['taskType', 'action', 'weight'],
169
+ },
170
+ async execute(input) {
171
+ const taskType = input.taskType;
172
+ let policy = policies.get(taskType);
173
+ if (!policy) {
174
+ policy = { taskType, preferredActions: [], weights: {} };
175
+ policies.set(taskType, policy);
176
+ }
177
+ const action = input.action;
178
+ policy.weights[action] = (policy.weights[action] || 0) + input.weight;
179
+ policy.preferredActions = Object.entries(policy.weights)
180
+ .sort(([, a], [, b]) => b - a)
181
+ .map(([k]) => k);
182
+ return { content: JSON.stringify({ taskType, preferredActions: policy.preferredActions.slice(0, 5), weights: policy.weights }) };
183
+ },
184
+ };
185
+ exports.rlGetStatistics = {
186
+ name: 'rl_get_statistics',
187
+ description: 'Get success/failure stats by task type',
188
+ inputSchema: {
189
+ type: 'object',
190
+ properties: { taskType: { type: 'string' } },
191
+ },
192
+ async execute(input) {
193
+ let data = trajectories;
194
+ if (input.taskType)
195
+ data = data.filter(t => t.taskType === input.taskType);
196
+ const stats = {};
197
+ for (const t of data) {
198
+ if (!stats[t.taskType])
199
+ stats[t.taskType] = { total: 0, success: 0, partial: 0, failure: 0, avgReward: 0 };
200
+ const s = stats[t.taskType];
201
+ s.total++;
202
+ if (t.outcome === 'success')
203
+ s.success++;
204
+ else if (t.outcome === 'partial')
205
+ s.partial++;
206
+ else if (t.outcome === 'failure')
207
+ s.failure++;
208
+ s.avgReward = (s.avgReward * (s.total - 1) + t.totalReward) / s.total;
209
+ }
210
+ return { content: JSON.stringify(stats) };
211
+ },
212
+ };
213
+ exports.rlResetEpisode = {
214
+ name: 'rl_reset_episode',
215
+ description: 'Clear current episode state',
216
+ inputSchema: { type: 'object', properties: {} },
217
+ async execute() {
218
+ const had = currentEpisode != null;
219
+ currentEpisode = null;
220
+ return { content: JSON.stringify({ reset: true, hadActiveEpisode: had }) };
221
+ },
222
+ };
223
+ exports.rlTools = [
224
+ exports.rlRecordTrajectory, exports.rlEvaluateOutcome, exports.rlGetBestStrategy, exports.rlCompareStrategies,
225
+ exports.rlGenerateTrainingData, exports.rlRewardSignal, exports.rlExplorationSuggest, exports.rlUpdatePolicy,
226
+ exports.rlGetStatistics, exports.rlResetEpisode,
227
+ ];
228
+ //# sourceMappingURL=rl-tools.js.map
@@ -0,0 +1,6 @@
1
+ import type { MCPTool } from '../mcp';
2
+ export declare const visionAnalyzeTool: MCPTool;
3
+ export declare const visionExtractTextTool: MCPTool;
4
+ export declare const visionCompareTool: MCPTool;
5
+ export declare const visionTools: MCPTool[];
6
+ //# sourceMappingURL=vision.d.ts.map
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.visionTools = exports.visionCompareTool = exports.visionExtractTextTool = exports.visionAnalyzeTool = void 0;
4
+ const vision_1 = require("../../core/vision");
5
+ const manager = new vision_1.VisionManager();
6
+ exports.visionAnalyzeTool = {
7
+ name: 'vision_analyze',
8
+ description: 'Analyze an image using vision AI. Provide image as URL or base64.',
9
+ inputSchema: {
10
+ type: 'object',
11
+ properties: {
12
+ image_url: { type: 'string', description: 'URL of the image to analyze' },
13
+ image_base64: { type: 'string', description: 'Base64-encoded image data' },
14
+ prompt: { type: 'string', description: 'Optional prompt for analysis' },
15
+ },
16
+ },
17
+ async execute(input) {
18
+ const imgInput = input.image_url
19
+ ? { type: 'url', data: input.image_url }
20
+ : { type: 'base64', data: input.image_base64 };
21
+ const result = await manager.analyze(imgInput, input.prompt);
22
+ return { content: JSON.stringify(result) };
23
+ },
24
+ };
25
+ exports.visionExtractTextTool = {
26
+ name: 'vision_extract_text',
27
+ description: 'Extract text (OCR) from an image.',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ image_url: { type: 'string', description: 'URL of the image' },
32
+ image_base64: { type: 'string', description: 'Base64-encoded image data' },
33
+ },
34
+ },
35
+ async execute(input) {
36
+ const imgInput = input.image_url
37
+ ? { type: 'url', data: input.image_url }
38
+ : { type: 'base64', data: input.image_base64 };
39
+ const text = await manager.extractText(imgInput);
40
+ return { content: text };
41
+ },
42
+ };
43
+ exports.visionCompareTool = {
44
+ name: 'vision_compare',
45
+ description: 'Compare multiple images.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ image_urls: { type: 'array', items: { type: 'string' }, description: 'URLs of images to compare' },
50
+ prompt: { type: 'string', description: 'Optional comparison prompt' },
51
+ },
52
+ },
53
+ async execute(input) {
54
+ const urls = input.image_urls;
55
+ const images = urls.map(url => ({ type: 'url', data: url }));
56
+ const result = await manager.compareImages(images, input.prompt);
57
+ return { content: result };
58
+ },
59
+ };
60
+ exports.visionTools = [exports.visionAnalyzeTool, exports.visionExtractTextTool, exports.visionCompareTool];
61
+ //# sourceMappingURL=vision.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opc-agent",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "Open Agent Framework — Build, test, and run AI Agents for business workstations",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -32,8 +32,8 @@
32
32
  "url": "https://github.com/Deepleaper/opc-agent.git"
33
33
  },
34
34
  "dependencies": {
35
- "agent-workstation": "^1.2.1",
36
- "agentkits": "^0.1.0",
35
+ "agent-workstation": "^2.0.1",
36
+ "agentkits": "^2.0.0",
37
37
  "commander": "^12.0.0",
38
38
  "express": "^4.21.0",
39
39
  "js-yaml": "^4.1.0",
@@ -0,0 +1,46 @@
1
+ import { BaseChannel } from './index';
2
+ import type { Message } from '../core/types';
3
+
4
+ export interface DingTalkChannelConfig {
5
+ webhookUrl: string;
6
+ secret?: string;
7
+ appKey?: string;
8
+ appSecret?: string;
9
+ }
10
+
11
+ export class DingTalkChannel extends BaseChannel {
12
+ readonly type = 'dingtalk';
13
+ private config: DingTalkChannelConfig;
14
+ private running = false;
15
+
16
+ constructor(config: DingTalkChannelConfig) {
17
+ super();
18
+ if (!config.webhookUrl) {
19
+ throw new Error('DingTalkChannel requires webhookUrl in config');
20
+ }
21
+ this.config = config;
22
+ }
23
+
24
+ async start(): Promise<void> {
25
+ this.running = true;
26
+ }
27
+
28
+ async stop(): Promise<void> {
29
+ this.running = false;
30
+ }
31
+
32
+ async send(chatId: string, text: string): Promise<void> {
33
+ if (!this.running) {
34
+ throw new Error('DingTalkChannel: not started. Call start() first.');
35
+ }
36
+ const body = JSON.stringify({ msgtype: 'text', text: { content: text } });
37
+ const res = await fetch(this.config.webhookUrl, {
38
+ method: 'POST',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body,
41
+ });
42
+ if (!res.ok) {
43
+ throw new Error(`DingTalk webhook failed: ${res.status}`);
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,42 @@
1
+ import { BaseChannel } from './index';
2
+ import type { Message } from '../core/types';
3
+
4
+ export interface GoogleChatChannelConfig {
5
+ webhookUrl: string;
6
+ }
7
+
8
+ export class GoogleChatChannel extends BaseChannel {
9
+ readonly type = 'googlechat';
10
+ private config: GoogleChatChannelConfig;
11
+ private running = false;
12
+
13
+ constructor(config: GoogleChatChannelConfig) {
14
+ super();
15
+ if (!config.webhookUrl) {
16
+ throw new Error('GoogleChatChannel requires webhookUrl in config');
17
+ }
18
+ this.config = config;
19
+ }
20
+
21
+ async start(): Promise<void> {
22
+ this.running = true;
23
+ }
24
+
25
+ async stop(): Promise<void> {
26
+ this.running = false;
27
+ }
28
+
29
+ async send(spaceId: string, text: string): Promise<void> {
30
+ if (!this.running) {
31
+ throw new Error('GoogleChatChannel: not started. Call start() first.');
32
+ }
33
+ const res = await fetch(this.config.webhookUrl, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({ text }),
37
+ });
38
+ if (!res.ok) {
39
+ throw new Error(`Google Chat webhook failed: ${res.status}`);
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,32 @@
1
+ import { BaseChannel } from './index';
2
+ import type { Message } from '../core/types';
3
+
4
+ export interface IMessageChannelConfig {
5
+ applescriptPath?: string;
6
+ }
7
+
8
+ export class IMessageChannel extends BaseChannel {
9
+ readonly type = 'imessage';
10
+ private config: IMessageChannelConfig;
11
+
12
+ constructor(config: IMessageChannelConfig = {}) {
13
+ super();
14
+ this.config = config;
15
+ }
16
+
17
+ async start(): Promise<void> {
18
+ try {
19
+ require('macOS iMessage CLI');
20
+ } catch {
21
+ throw new Error('Install macOS iMessage CLI to use the IMessageChannel. Run: npm install macOS iMessage CLI');
22
+ }
23
+ }
24
+
25
+ async stop(): Promise<void> {
26
+ // cleanup
27
+ }
28
+
29
+ async send(chatId: string, text: string): Promise<void> {
30
+ throw new Error('IMessageChannel: not yet connected. Call start() first.');
31
+ }
32
+ }
@@ -0,0 +1,82 @@
1
+ import { BaseChannel } from './index';
2
+ import type { Message } from '../core/types';
3
+
4
+ export interface IRCChannelConfig {
5
+ host: string;
6
+ port?: number;
7
+ nick: string;
8
+ channels: string[];
9
+ tls?: boolean;
10
+ password?: string;
11
+ }
12
+
13
+ export class IRCChannel extends BaseChannel {
14
+ readonly type = 'irc';
15
+ private config: IRCChannelConfig;
16
+ private client: any = null;
17
+ private running = false;
18
+
19
+ constructor(config: IRCChannelConfig) {
20
+ super();
21
+ if (!config.host || !config.nick || !config.channels?.length) {
22
+ throw new Error('IRCChannel requires host, nick, and channels in config');
23
+ }
24
+ this.config = config;
25
+ }
26
+
27
+ async start(): Promise<void> {
28
+ let IRC: any;
29
+ try {
30
+ IRC = require('irc-framework');
31
+ } catch {
32
+ throw new Error('Install irc-framework to use the IRCChannel. Run: npm install irc-framework');
33
+ }
34
+ this.client = new IRC.Client();
35
+ this.client.connect({
36
+ host: this.config.host,
37
+ port: this.config.port ?? (this.config.tls ? 6697 : 6667),
38
+ nick: this.config.nick,
39
+ tls: this.config.tls ?? false,
40
+ password: this.config.password,
41
+ });
42
+
43
+ await new Promise<void>((resolve, reject) => {
44
+ this.client.on('registered', () => {
45
+ for (const ch of this.config.channels) {
46
+ this.client.join(ch);
47
+ }
48
+ resolve();
49
+ });
50
+ this.client.on('error', (err: any) => reject(new Error(`IRC connection error: ${err.message || err}`)));
51
+ });
52
+
53
+ this.running = true;
54
+
55
+ this.client.on('privmsg', async (event: any) => {
56
+ if (!this.handler) return;
57
+ const msg: Message = {
58
+ id: Date.now().toString(),
59
+ role: 'user',
60
+ content: event.message,
61
+ timestamp: Date.now(),
62
+ metadata: { channel: event.target, nick: event.nick },
63
+ };
64
+ await this.handler(msg);
65
+ });
66
+ }
67
+
68
+ async stop(): Promise<void> {
69
+ if (this.client) {
70
+ this.client.quit('Goodbye');
71
+ this.client = null;
72
+ }
73
+ this.running = false;
74
+ }
75
+
76
+ async send(target: string, text: string): Promise<void> {
77
+ if (!this.running || !this.client) {
78
+ throw new Error('IRCChannel: not started. Call start() first.');
79
+ }
80
+ this.client.say(target, text);
81
+ }
82
+ }
@@ -0,0 +1,33 @@
1
+ import { BaseChannel } from './index';
2
+ import type { Message } from '../core/types';
3
+
4
+ export interface LINEChannelConfig {
5
+ channelAccessToken?: string;
6
+ channelSecret?: string;
7
+ }
8
+
9
+ export class LINEChannel extends BaseChannel {
10
+ readonly type = 'line';
11
+ private config: LINEChannelConfig;
12
+
13
+ constructor(config: LINEChannelConfig = {}) {
14
+ super();
15
+ this.config = config;
16
+ }
17
+
18
+ async start(): Promise<void> {
19
+ try {
20
+ require('@line/bot-sdk');
21
+ } catch {
22
+ throw new Error('Install @line/bot-sdk to use the LINEChannel. Run: npm install @line/bot-sdk');
23
+ }
24
+ }
25
+
26
+ async stop(): Promise<void> {
27
+ // cleanup
28
+ }
29
+
30
+ async send(chatId: string, text: string): Promise<void> {
31
+ throw new Error('LINEChannel: not yet connected. Call start() first.');
32
+ }
33
+ }
@@ -0,0 +1,34 @@
1
+ import { BaseChannel } from './index';
2
+ import type { Message } from '../core/types';
3
+
4
+ export interface MatrixChannelConfig {
5
+ homeserverUrl?: string;
6
+ accessToken?: string;
7
+ userId?: string;
8
+ }
9
+
10
+ export class MatrixChannel extends BaseChannel {
11
+ readonly type = 'matrix';
12
+ private config: MatrixChannelConfig;
13
+
14
+ constructor(config: MatrixChannelConfig = {}) {
15
+ super();
16
+ this.config = config;
17
+ }
18
+
19
+ async start(): Promise<void> {
20
+ try {
21
+ require('matrix-js-sdk');
22
+ } catch {
23
+ throw new Error('Install matrix-js-sdk to use the MatrixChannel. Run: npm install matrix-js-sdk');
24
+ }
25
+ }
26
+
27
+ async stop(): Promise<void> {
28
+ // cleanup
29
+ }
30
+
31
+ async send(chatId: string, text: string): Promise<void> {
32
+ throw new Error('MatrixChannel: not yet connected. Call start() first.');
33
+ }
34
+ }
@@ -0,0 +1,57 @@
1
+ import { BaseChannel } from './index';
2
+ import type { Message } from '../core/types';
3
+
4
+ export interface MattermostChannelConfig {
5
+ serverUrl: string;
6
+ token: string;
7
+ teamId?: string;
8
+ defaultChannelId?: string;
9
+ }
10
+
11
+ export class MattermostChannel extends BaseChannel {
12
+ readonly type = 'mattermost';
13
+ private config: MattermostChannelConfig;
14
+ private running = false;
15
+ private ws: any = null;
16
+
17
+ constructor(config: MattermostChannelConfig) {
18
+ super();
19
+ if (!config.serverUrl || !config.token) {
20
+ throw new Error('MattermostChannel requires serverUrl and token in config');
21
+ }
22
+ this.config = config;
23
+ }
24
+
25
+ async start(): Promise<void> {
26
+ // Verify connection by hitting the API
27
+ const res = await fetch(`${this.config.serverUrl}/api/v4/users/me`, {
28
+ headers: { Authorization: `Bearer ${this.config.token}` },
29
+ }).catch(() => null);
30
+ this.running = true;
31
+ }
32
+
33
+ async stop(): Promise<void> {
34
+ if (this.ws) {
35
+ this.ws.close();
36
+ this.ws = null;
37
+ }
38
+ this.running = false;
39
+ }
40
+
41
+ async send(channelId: string, text: string): Promise<void> {
42
+ if (!this.running) {
43
+ throw new Error('MattermostChannel: not started. Call start() first.');
44
+ }
45
+ const res = await fetch(`${this.config.serverUrl}/api/v4/posts`, {
46
+ method: 'POST',
47
+ headers: {
48
+ Authorization: `Bearer ${this.config.token}`,
49
+ 'Content-Type': 'application/json',
50
+ },
51
+ body: JSON.stringify({ channel_id: channelId, message: text }),
52
+ });
53
+ if (!res.ok) {
54
+ throw new Error(`Mattermost API failed: ${res.status}`);
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,33 @@
1
+ import { BaseChannel } from './index';
2
+ import type { Message } from '../core/types';
3
+
4
+ export interface MSTeamsChannelConfig {
5
+ appId?: string;
6
+ appPassword?: string;
7
+ }
8
+
9
+ export class MSTeamsChannel extends BaseChannel {
10
+ readonly type = 'msteams';
11
+ private config: MSTeamsChannelConfig;
12
+
13
+ constructor(config: MSTeamsChannelConfig = {}) {
14
+ super();
15
+ this.config = config;
16
+ }
17
+
18
+ async start(): Promise<void> {
19
+ try {
20
+ require('botframework-connector');
21
+ } catch {
22
+ throw new Error('Install botframework-connector to use the MSTeamsChannel. Run: npm install botframework-connector');
23
+ }
24
+ }
25
+
26
+ async stop(): Promise<void> {
27
+ // cleanup
28
+ }
29
+
30
+ async send(chatId: string, text: string): Promise<void> {
31
+ throw new Error('MSTeamsChannel: not yet connected. Call start() first.');
32
+ }
33
+ }