opc-agent 1.3.2 → 2.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 (226) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  4. package/.github/workflows/ci.yml +24 -0
  5. package/CHANGELOG.md +48 -63
  6. package/CONTRIBUTING.md +21 -60
  7. package/README.md +284 -348
  8. package/README.zh-CN.md +415 -415
  9. package/dist/channels/slack.js +93 -10
  10. package/dist/channels/telegram.d.ts +30 -9
  11. package/dist/channels/telegram.js +125 -33
  12. package/dist/channels/web.d.ts +10 -0
  13. package/dist/channels/web.js +33 -2
  14. package/dist/cli.js +667 -65
  15. package/dist/core/agent.d.ts +23 -0
  16. package/dist/core/agent.js +120 -3
  17. package/dist/core/runtime.d.ts +5 -0
  18. package/dist/core/runtime.js +71 -0
  19. package/dist/core/scheduler.d.ts +52 -0
  20. package/dist/core/scheduler.js +168 -0
  21. package/dist/core/subagent.d.ts +28 -0
  22. package/dist/core/subagent.js +65 -0
  23. package/dist/daemon.d.ts +3 -0
  24. package/dist/daemon.js +134 -0
  25. package/dist/deploy/hermes.js +22 -22
  26. package/dist/deploy/openclaw.js +31 -40
  27. package/dist/index.d.ts +10 -10
  28. package/dist/index.js +22 -15
  29. package/dist/providers/index.d.ts +6 -2
  30. package/dist/providers/index.js +22 -9
  31. package/dist/schema/oad.d.ts +180 -6
  32. package/dist/schema/oad.js +12 -1
  33. package/dist/skills/auto-learn.d.ts +28 -0
  34. package/dist/skills/auto-learn.js +257 -0
  35. package/dist/templates/code-reviewer.d.ts +0 -8
  36. package/dist/templates/code-reviewer.js +5 -9
  37. package/dist/templates/customer-service.d.ts +0 -8
  38. package/dist/templates/customer-service.js +2 -6
  39. package/dist/templates/data-analyst.d.ts +0 -8
  40. package/dist/templates/data-analyst.js +5 -9
  41. package/dist/templates/knowledge-base.d.ts +0 -8
  42. package/dist/templates/knowledge-base.js +2 -6
  43. package/dist/templates/sales-assistant.d.ts +0 -8
  44. package/dist/templates/sales-assistant.js +4 -8
  45. package/dist/templates/teacher.d.ts +0 -8
  46. package/dist/templates/teacher.js +6 -10
  47. package/dist/tools/builtin/datetime.d.ts +3 -0
  48. package/dist/tools/builtin/datetime.js +44 -0
  49. package/dist/tools/builtin/file.d.ts +3 -0
  50. package/dist/tools/builtin/file.js +151 -0
  51. package/dist/tools/builtin/index.d.ts +15 -0
  52. package/dist/tools/builtin/index.js +30 -0
  53. package/dist/tools/builtin/shell.d.ts +3 -0
  54. package/dist/tools/builtin/shell.js +43 -0
  55. package/dist/tools/builtin/web.d.ts +3 -0
  56. package/dist/tools/builtin/web.js +37 -0
  57. package/dist/tools/mcp-client.d.ts +24 -0
  58. package/dist/tools/mcp-client.js +119 -0
  59. package/dist/traces/index.d.ts +49 -0
  60. package/dist/traces/index.js +102 -0
  61. package/docs/.vitepress/config.ts +103 -103
  62. package/docs/api/cli.md +48 -48
  63. package/docs/api/oad-schema.md +64 -64
  64. package/docs/api/sdk.md +80 -80
  65. package/docs/guide/concepts.md +51 -51
  66. package/docs/guide/configuration.md +79 -79
  67. package/docs/guide/deployment.md +42 -42
  68. package/docs/guide/getting-started.md +44 -44
  69. package/docs/guide/templates.md +28 -28
  70. package/docs/guide/testing.md +84 -84
  71. package/docs/index.md +27 -27
  72. package/docs/zh/api/cli.md +54 -54
  73. package/docs/zh/api/oad-schema.md +87 -87
  74. package/docs/zh/api/sdk.md +102 -102
  75. package/docs/zh/guide/concepts.md +104 -104
  76. package/docs/zh/guide/configuration.md +135 -135
  77. package/docs/zh/guide/deployment.md +81 -81
  78. package/docs/zh/guide/getting-started.md +82 -82
  79. package/docs/zh/guide/templates.md +84 -84
  80. package/docs/zh/guide/testing.md +88 -88
  81. package/docs/zh/index.md +27 -27
  82. package/examples/README.md +22 -0
  83. package/examples/basic-agent.ts +90 -0
  84. package/examples/brain-integration.ts +71 -0
  85. package/examples/customer-service-demo/README.md +90 -90
  86. package/examples/customer-service-demo/oad.yaml +107 -107
  87. package/examples/multi-channel.ts +74 -0
  88. package/package.json +1 -1
  89. package/src/analytics/index.ts +66 -66
  90. package/src/channels/discord.ts +192 -192
  91. package/src/channels/email.ts +177 -177
  92. package/src/channels/feishu.ts +236 -236
  93. package/src/channels/index.ts +15 -15
  94. package/src/channels/slack.ts +217 -160
  95. package/src/channels/telegram.ts +155 -33
  96. package/src/channels/voice.ts +106 -106
  97. package/src/channels/web.ts +38 -2
  98. package/src/channels/webhook.ts +199 -199
  99. package/src/channels/websocket.ts +87 -87
  100. package/src/channels/wechat.ts +149 -149
  101. package/src/cli.ts +697 -63
  102. package/src/core/a2a.ts +143 -143
  103. package/src/core/agent.ts +146 -3
  104. package/src/core/analytics-engine.ts +186 -186
  105. package/src/core/auth.ts +57 -57
  106. package/src/core/cache.ts +141 -141
  107. package/src/core/compose.ts +77 -77
  108. package/src/core/config.ts +14 -14
  109. package/src/core/errors.ts +148 -148
  110. package/src/core/hitl.ts +138 -138
  111. package/src/core/logger.ts +57 -57
  112. package/src/core/orchestrator.ts +215 -215
  113. package/src/core/performance.ts +187 -187
  114. package/src/core/rate-limiter.ts +128 -128
  115. package/src/core/room.ts +109 -109
  116. package/src/core/runtime.ts +230 -152
  117. package/src/core/sandbox.ts +101 -101
  118. package/src/core/scheduler.ts +187 -0
  119. package/src/core/security.ts +171 -171
  120. package/src/core/subagent.ts +98 -0
  121. package/src/core/types.ts +68 -68
  122. package/src/core/versioning.ts +106 -106
  123. package/src/core/watch.ts +178 -178
  124. package/src/core/workflow.ts +235 -235
  125. package/src/daemon.ts +96 -0
  126. package/src/deploy/hermes.ts +156 -156
  127. package/src/deploy/openclaw.ts +190 -200
  128. package/src/i18n/index.ts +216 -216
  129. package/src/index.ts +14 -10
  130. package/src/memory/deepbrain.ts +108 -108
  131. package/src/memory/index.ts +34 -34
  132. package/src/plugins/index.ts +208 -208
  133. package/src/providers/index.ts +354 -331
  134. package/src/schema/oad.ts +14 -2
  135. package/src/skills/auto-learn.ts +262 -0
  136. package/src/skills/base.ts +16 -16
  137. package/src/skills/document.ts +100 -100
  138. package/src/skills/http.ts +35 -35
  139. package/src/skills/index.ts +27 -27
  140. package/src/skills/scheduler.ts +80 -80
  141. package/src/skills/webhook-trigger.ts +59 -59
  142. package/src/templates/code-reviewer.ts +30 -34
  143. package/src/templates/customer-service.ts +76 -80
  144. package/src/templates/data-analyst.ts +66 -70
  145. package/src/templates/executive-assistant.ts +71 -71
  146. package/src/templates/financial-advisor.ts +60 -60
  147. package/src/templates/knowledge-base.ts +27 -31
  148. package/src/templates/legal-assistant.ts +71 -71
  149. package/src/templates/sales-assistant.ts +75 -79
  150. package/src/templates/teacher.ts +75 -79
  151. package/src/testing/index.ts +181 -181
  152. package/src/tools/builtin/datetime.ts +41 -0
  153. package/src/tools/builtin/file.ts +107 -0
  154. package/src/tools/builtin/index.ts +28 -0
  155. package/src/tools/builtin/shell.ts +43 -0
  156. package/src/tools/builtin/web.ts +35 -0
  157. package/src/tools/calculator.ts +73 -73
  158. package/src/tools/datetime.ts +149 -149
  159. package/src/tools/json-transform.ts +187 -187
  160. package/src/tools/mcp-client.ts +131 -0
  161. package/src/tools/mcp.ts +76 -76
  162. package/src/tools/text-analysis.ts +116 -116
  163. package/src/traces/index.ts +132 -0
  164. package/templates/Dockerfile +15 -15
  165. package/templates/code-reviewer/README.md +27 -27
  166. package/templates/code-reviewer/oad.yaml +41 -41
  167. package/templates/customer-service/README.md +22 -22
  168. package/templates/customer-service/oad.yaml +36 -36
  169. package/templates/docker-compose.yml +21 -21
  170. package/templates/ecommerce-assistant/README.md +45 -45
  171. package/templates/ecommerce-assistant/oad.yaml +47 -47
  172. package/templates/knowledge-base/README.md +28 -28
  173. package/templates/knowledge-base/oad.yaml +38 -38
  174. package/templates/sales-assistant/README.md +26 -26
  175. package/templates/sales-assistant/oad.yaml +43 -43
  176. package/templates/tech-support/README.md +43 -43
  177. package/templates/tech-support/oad.yaml +45 -45
  178. package/test-agent/Dockerfile +9 -0
  179. package/test-agent/README.md +50 -0
  180. package/test-agent/agent.yaml +23 -0
  181. package/test-agent/docker-compose.yml +11 -0
  182. package/test-agent/oad.yaml +31 -0
  183. package/test-agent/package-lock.json +1492 -0
  184. package/test-agent/package.json +18 -0
  185. package/test-agent/src/index.ts +24 -0
  186. package/test-agent/src/skills/echo.ts +15 -0
  187. package/test-agent/tsconfig.json +25 -0
  188. package/tests/a2a.test.ts +66 -66
  189. package/tests/agent.test.ts +72 -72
  190. package/tests/analytics.test.ts +50 -50
  191. package/tests/auto-learn.test.ts +105 -0
  192. package/tests/builtin-tools.test.ts +83 -0
  193. package/tests/channel.test.ts +39 -39
  194. package/tests/cli.test.ts +46 -0
  195. package/tests/e2e.test.ts +134 -134
  196. package/tests/errors.test.ts +83 -83
  197. package/tests/hitl.test.ts +71 -71
  198. package/tests/i18n.test.ts +41 -41
  199. package/tests/mcp.test.ts +54 -54
  200. package/tests/oad.test.ts +68 -68
  201. package/tests/performance.test.ts +115 -115
  202. package/tests/plugin.test.ts +74 -74
  203. package/tests/room.test.ts +106 -106
  204. package/tests/runtime.test.ts +42 -42
  205. package/tests/sandbox.test.ts +46 -46
  206. package/tests/security.test.ts +60 -60
  207. package/tests/subagent.test.ts +130 -0
  208. package/tests/telegram-discord.test.ts +60 -0
  209. package/tests/templates.test.ts +77 -77
  210. package/tests/v070.test.ts +76 -76
  211. package/tests/versioning.test.ts +75 -75
  212. package/tests/voice.test.ts +61 -61
  213. package/tests/webhook.test.ts +29 -29
  214. package/tests/workflow.test.ts +143 -143
  215. package/tsconfig.json +19 -19
  216. package/vitest.config.ts +9 -9
  217. package/dist/core/dashboard.d.ts +0 -35
  218. package/dist/core/dashboard.js +0 -157
  219. package/dist/core/priority.d.ts +0 -52
  220. package/dist/core/priority.js +0 -102
  221. package/src/core/dashboard.ts +0 -219
  222. package/src/core/priority.ts +0 -140
  223. package/src/dtv/data.ts +0 -29
  224. package/src/dtv/trust.ts +0 -43
  225. package/src/dtv/value.ts +0 -47
  226. package/src/marketplace/index.ts +0 -223
@@ -1,42 +1,42 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { AgentRuntime } from '../src/core/runtime';
3
- import { createCustomerServiceConfig } from '../src/templates/customer-service';
4
-
5
- describe('AgentRuntime', () => {
6
- it('should initialize from config object', async () => {
7
- const runtime = new AgentRuntime();
8
- const config = createCustomerServiceConfig();
9
- const agent = await runtime.initialize(config);
10
- expect(agent.name).toBe('customer-service');
11
- expect(agent.state).toBe('ready');
12
- });
13
-
14
- it('should throw if no config loaded', async () => {
15
- const runtime = new AgentRuntime();
16
- await expect(runtime.initialize()).rejects.toThrow('No config loaded');
17
- });
18
-
19
- it('should register skills after initialization', async () => {
20
- const runtime = new AgentRuntime();
21
- const config = createCustomerServiceConfig();
22
- await runtime.initialize(config);
23
-
24
- runtime.registerSkill({
25
- name: 'test-skill',
26
- description: 'Test',
27
- execute: async () => ({ handled: false, confidence: 0 }),
28
- });
29
- // No throw = success
30
- });
31
-
32
- it('should throw registering skill before init', () => {
33
- const runtime = new AgentRuntime();
34
- expect(() =>
35
- runtime.registerSkill({
36
- name: 'test',
37
- description: 'Test',
38
- execute: async () => ({ handled: false, confidence: 0 }),
39
- })
40
- ).toThrow('Agent not initialized');
41
- });
42
- });
1
+ import { describe, it, expect } from 'vitest';
2
+ import { AgentRuntime } from '../src/core/runtime';
3
+ import { createCustomerServiceConfig } from '../src/templates/customer-service';
4
+
5
+ describe('AgentRuntime', () => {
6
+ it('should initialize from config object', async () => {
7
+ const runtime = new AgentRuntime();
8
+ const config = createCustomerServiceConfig();
9
+ const agent = await runtime.initialize(config);
10
+ expect(agent.name).toBe('customer-service');
11
+ expect(agent.state).toBe('ready');
12
+ });
13
+
14
+ it('should throw if no config loaded', async () => {
15
+ const runtime = new AgentRuntime();
16
+ await expect(runtime.initialize()).rejects.toThrow('No config loaded');
17
+ });
18
+
19
+ it('should register skills after initialization', async () => {
20
+ const runtime = new AgentRuntime();
21
+ const config = createCustomerServiceConfig();
22
+ await runtime.initialize(config);
23
+
24
+ runtime.registerSkill({
25
+ name: 'test-skill',
26
+ description: 'Test',
27
+ execute: async () => ({ handled: false, confidence: 0 }),
28
+ });
29
+ // No throw = success
30
+ });
31
+
32
+ it('should throw registering skill before init', () => {
33
+ const runtime = new AgentRuntime();
34
+ expect(() =>
35
+ runtime.registerSkill({
36
+ name: 'test',
37
+ description: 'Test',
38
+ execute: async () => ({ handled: false, confidence: 0 }),
39
+ })
40
+ ).toThrow('Agent not initialized');
41
+ });
42
+ });
@@ -1,46 +1,46 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Sandbox } from '../src/core/sandbox';
3
-
4
- describe('Security Sandbox', () => {
5
- it('should create sandbox with trust level', () => {
6
- const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
7
- expect(sb.trustLevel).toBe('sandbox');
8
- });
9
-
10
- it('should restrict shell in sandbox mode', () => {
11
- const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
12
- expect(sb.checkShellAccess()).toBe(false);
13
- });
14
-
15
- it('should allow shell in certified mode', () => {
16
- const sb = new Sandbox({ trustLevel: 'certified', agentDir: '/tmp/agent' });
17
- expect(sb.checkShellAccess()).toBe(true);
18
- });
19
-
20
- it('should restrict network in sandbox mode', () => {
21
- const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
22
- expect(sb.checkNetworkAccess('https://api.openai.com')).toBe(false);
23
- });
24
-
25
- it('should allow network with allowlist', () => {
26
- const sb = new Sandbox({
27
- trustLevel: 'sandbox',
28
- agentDir: '/tmp/agent',
29
- networkAllowlist: ['api.openai.com'],
30
- });
31
- expect(sb.checkNetworkAccess('https://api.openai.com/v1/chat')).toBe(true);
32
- expect(sb.checkNetworkAccess('https://evil.com')).toBe(false);
33
- });
34
-
35
- it('should allow wildcard network in listed mode', () => {
36
- const sb = new Sandbox({ trustLevel: 'listed', agentDir: '/tmp/agent' });
37
- expect(sb.checkNetworkAccess('https://anything.com')).toBe(true);
38
- });
39
-
40
- it('should return restrictions snapshot', () => {
41
- const sb = new Sandbox({ trustLevel: 'verified', agentDir: '/tmp/agent' });
42
- const r = sb.getRestrictions();
43
- expect(r.shell).toBe(false);
44
- expect(r.network.allowed.length).toBeGreaterThan(0);
45
- });
46
- });
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Sandbox } from '../src/core/sandbox';
3
+
4
+ describe('Security Sandbox', () => {
5
+ it('should create sandbox with trust level', () => {
6
+ const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
7
+ expect(sb.trustLevel).toBe('sandbox');
8
+ });
9
+
10
+ it('should restrict shell in sandbox mode', () => {
11
+ const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
12
+ expect(sb.checkShellAccess()).toBe(false);
13
+ });
14
+
15
+ it('should allow shell in certified mode', () => {
16
+ const sb = new Sandbox({ trustLevel: 'certified', agentDir: '/tmp/agent' });
17
+ expect(sb.checkShellAccess()).toBe(true);
18
+ });
19
+
20
+ it('should restrict network in sandbox mode', () => {
21
+ const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
22
+ expect(sb.checkNetworkAccess('https://api.openai.com')).toBe(false);
23
+ });
24
+
25
+ it('should allow network with allowlist', () => {
26
+ const sb = new Sandbox({
27
+ trustLevel: 'sandbox',
28
+ agentDir: '/tmp/agent',
29
+ networkAllowlist: ['api.openai.com'],
30
+ });
31
+ expect(sb.checkNetworkAccess('https://api.openai.com/v1/chat')).toBe(true);
32
+ expect(sb.checkNetworkAccess('https://evil.com')).toBe(false);
33
+ });
34
+
35
+ it('should allow wildcard network in listed mode', () => {
36
+ const sb = new Sandbox({ trustLevel: 'listed', agentDir: '/tmp/agent' });
37
+ expect(sb.checkNetworkAccess('https://anything.com')).toBe(true);
38
+ });
39
+
40
+ it('should return restrictions snapshot', () => {
41
+ const sb = new Sandbox({ trustLevel: 'verified', agentDir: '/tmp/agent' });
42
+ const r = sb.getRestrictions();
43
+ expect(r.shell).toBe(false);
44
+ expect(r.network.allowed.length).toBeGreaterThan(0);
45
+ });
46
+ });
@@ -1,60 +1,60 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { sanitizeInput, detectInjection, APIKeyManager } from '../src/core/security';
3
-
4
- describe('Security', () => {
5
- describe('sanitizeInput', () => {
6
- it('strips script tags', () => {
7
- expect(sanitizeInput('<script>alert(1)</script>hello')).not.toContain('<script');
8
- });
9
- it('encodes HTML entities', () => {
10
- const result = sanitizeInput('a < b & c > d');
11
- expect(result).toContain('&lt;');
12
- expect(result).toContain('&amp;');
13
- expect(result).toContain('&gt;');
14
- });
15
- });
16
-
17
- describe('detectInjection', () => {
18
- it('detects XSS', () => {
19
- const r = detectInjection('<script>alert(1)</script>');
20
- expect(r.safe).toBe(false);
21
- expect(r.threats).toContain('xss');
22
- });
23
- it('passes clean input', () => {
24
- expect(detectInjection('Hello world')).toEqual({ safe: true, threats: [] });
25
- });
26
- });
27
-
28
- describe('APIKeyManager', () => {
29
- it('add, validate, revoke', () => {
30
- const mgr = new APIKeyManager();
31
- mgr.addKey('key1', { label: 'test' });
32
- expect(mgr.isValid('key1')).toBe(true);
33
- expect(mgr.isValid('key2')).toBe(false);
34
- mgr.revokeKey('key1');
35
- expect(mgr.isValid('key1')).toBe(false);
36
- });
37
-
38
- it('rotate key', () => {
39
- const mgr = new APIKeyManager();
40
- mgr.addKey('old');
41
- expect(mgr.rotateKey('old', 'new')).toBe(true);
42
- expect(mgr.isValid('old')).toBe(false);
43
- expect(mgr.isValid('new')).toBe(true);
44
- });
45
-
46
- it('expires keys', () => {
47
- const mgr = new APIKeyManager();
48
- mgr.addKey('expired', { expiresAt: Date.now() - 1000 });
49
- expect(mgr.isValid('expired')).toBe(false);
50
- });
51
-
52
- it('listActive filters', () => {
53
- const mgr = new APIKeyManager();
54
- mgr.addKey('a');
55
- mgr.addKey('b');
56
- mgr.revokeKey('b');
57
- expect(mgr.listActive().length).toBe(1);
58
- });
59
- });
60
- });
1
+ import { describe, it, expect } from 'vitest';
2
+ import { sanitizeInput, detectInjection, APIKeyManager } from '../src/core/security';
3
+
4
+ describe('Security', () => {
5
+ describe('sanitizeInput', () => {
6
+ it('strips script tags', () => {
7
+ expect(sanitizeInput('<script>alert(1)</script>hello')).not.toContain('<script');
8
+ });
9
+ it('encodes HTML entities', () => {
10
+ const result = sanitizeInput('a < b & c > d');
11
+ expect(result).toContain('&lt;');
12
+ expect(result).toContain('&amp;');
13
+ expect(result).toContain('&gt;');
14
+ });
15
+ });
16
+
17
+ describe('detectInjection', () => {
18
+ it('detects XSS', () => {
19
+ const r = detectInjection('<script>alert(1)</script>');
20
+ expect(r.safe).toBe(false);
21
+ expect(r.threats).toContain('xss');
22
+ });
23
+ it('passes clean input', () => {
24
+ expect(detectInjection('Hello world')).toEqual({ safe: true, threats: [] });
25
+ });
26
+ });
27
+
28
+ describe('APIKeyManager', () => {
29
+ it('add, validate, revoke', () => {
30
+ const mgr = new APIKeyManager();
31
+ mgr.addKey('key1', { label: 'test' });
32
+ expect(mgr.isValid('key1')).toBe(true);
33
+ expect(mgr.isValid('key2')).toBe(false);
34
+ mgr.revokeKey('key1');
35
+ expect(mgr.isValid('key1')).toBe(false);
36
+ });
37
+
38
+ it('rotate key', () => {
39
+ const mgr = new APIKeyManager();
40
+ mgr.addKey('old');
41
+ expect(mgr.rotateKey('old', 'new')).toBe(true);
42
+ expect(mgr.isValid('old')).toBe(false);
43
+ expect(mgr.isValid('new')).toBe(true);
44
+ });
45
+
46
+ it('expires keys', () => {
47
+ const mgr = new APIKeyManager();
48
+ mgr.addKey('expired', { expiresAt: Date.now() - 1000 });
49
+ expect(mgr.isValid('expired')).toBe(false);
50
+ });
51
+
52
+ it('listActive filters', () => {
53
+ const mgr = new APIKeyManager();
54
+ mgr.addKey('a');
55
+ mgr.addKey('b');
56
+ mgr.revokeKey('b');
57
+ expect(mgr.listActive().length).toBe(1);
58
+ });
59
+ });
60
+ });
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { SubAgentManager } from '../src/core/subagent';
3
+ import { BaseAgent } from '../src/core/agent';
4
+
5
+ // Mock the provider so we don't need real API keys
6
+ vi.mock('../src/providers', () => ({
7
+ createProvider: vi.fn(() => ({
8
+ name: 'mock',
9
+ chat: vi.fn().mockResolvedValue('mock response'),
10
+ chatStream: vi.fn(),
11
+ })),
12
+ SUPPORTED_PROVIDERS: ['openai'],
13
+ }));
14
+
15
+ describe('SubAgentManager', () => {
16
+ let manager: SubAgentManager;
17
+
18
+ beforeEach(() => {
19
+ manager = new SubAgentManager();
20
+ });
21
+
22
+ it('should spawn a sub-agent and return result', async () => {
23
+ const result = await manager.spawn({
24
+ name: 'test-agent',
25
+ task: 'Hello',
26
+ });
27
+
28
+ expect(result.name).toBe('test-agent');
29
+ expect(result.status).toBe('completed');
30
+ expect(result.result).toBe('mock response');
31
+ expect(result.duration).toBeGreaterThanOrEqual(0);
32
+ expect(result.id).toMatch(/^sub_/);
33
+ });
34
+
35
+ it('should track agents in list', async () => {
36
+ await manager.spawn({ name: 'agent-1', task: 'task 1' });
37
+ await manager.spawn({ name: 'agent-2', task: 'task 2' });
38
+
39
+ const list = manager.list();
40
+ expect(list).toHaveLength(2);
41
+ expect(list[0].status).toBe('completed');
42
+ expect(list[1].status).toBe('completed');
43
+ });
44
+
45
+ it('should spawn parallel agents', async () => {
46
+ const results = await manager.spawnParallel([
47
+ { name: 'p1', task: 'task 1' },
48
+ { name: 'p2', task: 'task 2' },
49
+ { name: 'p3', task: 'task 3' },
50
+ ]);
51
+
52
+ expect(results).toHaveLength(3);
53
+ results.forEach((r) => {
54
+ expect(r.status).toBe('completed');
55
+ expect(r.result).toBe('mock response');
56
+ });
57
+ });
58
+
59
+ it('should kill a sub-agent', async () => {
60
+ const result = await manager.spawn({ name: 'killable', task: 'task' });
61
+
62
+ expect(manager.kill(result.id)).toBe(true);
63
+ const list = manager.list();
64
+ const killed = list.find((a) => a.id === result.id);
65
+ expect(killed?.status).toBe('killed');
66
+ });
67
+
68
+ it('should return false when killing non-existent agent', () => {
69
+ expect(manager.kill('non-existent')).toBe(false);
70
+ });
71
+
72
+ it('should handle timeout', async () => {
73
+ const { createProvider } = await import('../src/providers');
74
+ (createProvider as ReturnType<typeof vi.fn>).mockReturnValueOnce({
75
+ name: 'mock-slow',
76
+ chat: () => new Promise((resolve) => setTimeout(() => resolve('late'), 5000)),
77
+ chatStream: vi.fn(),
78
+ });
79
+
80
+ const result = await manager.spawn({
81
+ name: 'slow-agent',
82
+ task: 'slow task',
83
+ timeout: 50,
84
+ });
85
+
86
+ expect(result.status).toBe('timeout');
87
+ });
88
+
89
+ it('should handle failed agents', async () => {
90
+ const { createProvider } = await import('../src/providers');
91
+ (createProvider as ReturnType<typeof vi.fn>).mockReturnValueOnce({
92
+ name: 'mock-fail',
93
+ chat: () => Promise.reject(new Error('provider error')),
94
+ chatStream: vi.fn(),
95
+ });
96
+
97
+ const result = await manager.spawn({
98
+ name: 'fail-agent',
99
+ task: 'fail task',
100
+ });
101
+
102
+ expect(result.status).toBe('failed');
103
+ expect(result.result).toBe('provider error');
104
+ });
105
+
106
+ it('should list empty when no agents spawned', () => {
107
+ expect(manager.list()).toEqual([]);
108
+ });
109
+
110
+ it('should use isolated memory by default', async () => {
111
+ const result = await manager.spawn({
112
+ name: 'isolated',
113
+ task: 'test',
114
+ isolated: true,
115
+ });
116
+ expect(result.status).toBe('completed');
117
+ });
118
+ });
119
+
120
+ describe('BaseAgent subagent methods', () => {
121
+ it('should have spawnSubAgent method', () => {
122
+ const agent = new BaseAgent({ name: 'parent' });
123
+ expect(typeof agent.spawnSubAgent).toBe('function');
124
+ });
125
+
126
+ it('should have spawnParallel method', () => {
127
+ const agent = new BaseAgent({ name: 'parent' });
128
+ expect(typeof agent.spawnParallel).toBe('function');
129
+ });
130
+ });
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { TelegramChannel } from '../src/channels/telegram';
3
+ import { DiscordChannel } from '../src/channels/discord';
4
+
5
+ describe('TelegramChannel', () => {
6
+ it('should create with default config (polling mode)', () => {
7
+ const channel = new TelegramChannel({ token: 'test-token' });
8
+ expect(channel.type).toBe('telegram');
9
+ });
10
+
11
+ it('should create with webhook mode', () => {
12
+ const channel = new TelegramChannel({
13
+ token: 'test-token',
14
+ mode: 'webhook',
15
+ webhookUrl: 'https://example.com',
16
+ port: 4000,
17
+ });
18
+ expect(channel.type).toBe('telegram');
19
+ });
20
+
21
+ it('should warn and return if no token on start', async () => {
22
+ const channel = new TelegramChannel({ token: '' });
23
+ // Should not throw, just warn
24
+ await channel.start();
25
+ await channel.stop();
26
+ });
27
+
28
+ it('should support onMessage handler', () => {
29
+ const channel = new TelegramChannel({ token: 'test-token' });
30
+ const handler = async (msg: any) => ({ ...msg, role: 'assistant' as const });
31
+ channel.onMessage(handler);
32
+ // No throw = success
33
+ expect(channel.type).toBe('telegram');
34
+ });
35
+ });
36
+
37
+ describe('DiscordChannel', () => {
38
+ it('should create with default config', () => {
39
+ const channel = new DiscordChannel({ botToken: 'test-token' });
40
+ expect(channel.type).toBe('discord');
41
+ });
42
+
43
+ it('should warn and return if no token on start', async () => {
44
+ const channel = new DiscordChannel({ botToken: '' });
45
+ await channel.start();
46
+ await channel.stop();
47
+ });
48
+
49
+ it('should support onMessage handler', () => {
50
+ const channel = new DiscordChannel({ botToken: 'test-token' });
51
+ const handler = async (msg: any) => ({ ...msg, role: 'assistant' as const });
52
+ channel.onMessage(handler);
53
+ expect(channel.type).toBe('discord');
54
+ });
55
+
56
+ it('should handle stop gracefully when not started', async () => {
57
+ const channel = new DiscordChannel({ botToken: 'test-token' });
58
+ await channel.stop(); // Should not throw
59
+ });
60
+ });
@@ -1,77 +1,77 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createLegalAssistantConfig, ContractReviewSkill, ComplianceCheckSkill } from '../src/templates/legal-assistant';
3
- import { createFinancialAdvisorConfig, BudgetAnalysisSkill, FinancialPlanningSkill } from '../src/templates/financial-advisor';
4
- import { createExecutiveAssistantConfig, CalendarSkill, EmailDraftSkill, MeetingPrepSkill } from '../src/templates/executive-assistant';
5
- import type { AgentContext, Message, MemoryStore } from '../src/core/types';
6
-
7
- const mockMemory: MemoryStore = {
8
- get: async () => null, set: async () => {}, getConversation: async () => [],
9
- addMessage: async () => {}, clear: async () => {},
10
- };
11
- const ctx: AgentContext = { agentName: 'test', sessionId: 's1', messages: [], memory: mockMemory, metadata: {} };
12
- const msg = (content: string): Message => ({ id: '1', role: 'user', content, timestamp: Date.now() });
13
-
14
- describe('Legal Assistant Template', () => {
15
- it('should create valid config', () => {
16
- const config = createLegalAssistantConfig();
17
- expect(config.metadata.name).toBe('legal-assistant');
18
- expect(config.spec.skills).toHaveLength(2);
19
- });
20
-
21
- it('ContractReviewSkill matches contract terms', async () => {
22
- const skill = new ContractReviewSkill();
23
- const r = await skill.execute(ctx, msg('What is force majeure?'));
24
- expect(r.handled).toBe(true);
25
- });
26
-
27
- it('ComplianceCheckSkill matches GDPR', async () => {
28
- const skill = new ComplianceCheckSkill();
29
- const r = await skill.execute(ctx, msg('GDPR requirements'));
30
- expect(r.handled).toBe(true);
31
- });
32
- });
33
-
34
- describe('Financial Advisor Template', () => {
35
- it('should create valid config', () => {
36
- const config = createFinancialAdvisorConfig();
37
- expect(config.metadata.name).toBe('financial-advisor');
38
- });
39
-
40
- it('BudgetAnalysisSkill matches budget queries', async () => {
41
- const skill = new BudgetAnalysisSkill();
42
- const r = await skill.execute(ctx, msg('Help with my budget'));
43
- expect(r.handled).toBe(true);
44
- });
45
-
46
- it('FinancialPlanningSkill matches investment queries', async () => {
47
- const skill = new FinancialPlanningSkill();
48
- const r = await skill.execute(ctx, msg('How to invest'));
49
- expect(r.handled).toBe(true);
50
- });
51
- });
52
-
53
- describe('Executive Assistant Template', () => {
54
- it('should create valid config', () => {
55
- const config = createExecutiveAssistantConfig();
56
- expect(config.metadata.name).toBe('executive-assistant');
57
- expect(config.spec.skills).toHaveLength(3);
58
- });
59
-
60
- it('CalendarSkill matches scheduling', async () => {
61
- const skill = new CalendarSkill();
62
- const r = await skill.execute(ctx, msg('Schedule a meeting'));
63
- expect(r.handled).toBe(true);
64
- });
65
-
66
- it('EmailDraftSkill matches email queries', async () => {
67
- const skill = new EmailDraftSkill();
68
- const r = await skill.execute(ctx, msg('Draft an email'));
69
- expect(r.handled).toBe(true);
70
- });
71
-
72
- it('MeetingPrepSkill matches prep queries', async () => {
73
- const skill = new MeetingPrepSkill();
74
- const r = await skill.execute(ctx, msg('Prepare the agenda'));
75
- expect(r.handled).toBe(true);
76
- });
77
- });
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createLegalAssistantConfig, ContractReviewSkill, ComplianceCheckSkill } from '../src/templates/legal-assistant';
3
+ import { createFinancialAdvisorConfig, BudgetAnalysisSkill, FinancialPlanningSkill } from '../src/templates/financial-advisor';
4
+ import { createExecutiveAssistantConfig, CalendarSkill, EmailDraftSkill, MeetingPrepSkill } from '../src/templates/executive-assistant';
5
+ import type { AgentContext, Message, MemoryStore } from '../src/core/types';
6
+
7
+ const mockMemory: MemoryStore = {
8
+ get: async () => null, set: async () => {}, getConversation: async () => [],
9
+ addMessage: async () => {}, clear: async () => {},
10
+ };
11
+ const ctx: AgentContext = { agentName: 'test', sessionId: 's1', messages: [], memory: mockMemory, metadata: {} };
12
+ const msg = (content: string): Message => ({ id: '1', role: 'user', content, timestamp: Date.now() });
13
+
14
+ describe('Legal Assistant Template', () => {
15
+ it('should create valid config', () => {
16
+ const config = createLegalAssistantConfig();
17
+ expect(config.metadata.name).toBe('legal-assistant');
18
+ expect(config.spec.skills).toHaveLength(2);
19
+ });
20
+
21
+ it('ContractReviewSkill matches contract terms', async () => {
22
+ const skill = new ContractReviewSkill();
23
+ const r = await skill.execute(ctx, msg('What is force majeure?'));
24
+ expect(r.handled).toBe(true);
25
+ });
26
+
27
+ it('ComplianceCheckSkill matches GDPR', async () => {
28
+ const skill = new ComplianceCheckSkill();
29
+ const r = await skill.execute(ctx, msg('GDPR requirements'));
30
+ expect(r.handled).toBe(true);
31
+ });
32
+ });
33
+
34
+ describe('Financial Advisor Template', () => {
35
+ it('should create valid config', () => {
36
+ const config = createFinancialAdvisorConfig();
37
+ expect(config.metadata.name).toBe('financial-advisor');
38
+ });
39
+
40
+ it('BudgetAnalysisSkill matches budget queries', async () => {
41
+ const skill = new BudgetAnalysisSkill();
42
+ const r = await skill.execute(ctx, msg('Help with my budget'));
43
+ expect(r.handled).toBe(true);
44
+ });
45
+
46
+ it('FinancialPlanningSkill matches investment queries', async () => {
47
+ const skill = new FinancialPlanningSkill();
48
+ const r = await skill.execute(ctx, msg('How to invest'));
49
+ expect(r.handled).toBe(true);
50
+ });
51
+ });
52
+
53
+ describe('Executive Assistant Template', () => {
54
+ it('should create valid config', () => {
55
+ const config = createExecutiveAssistantConfig();
56
+ expect(config.metadata.name).toBe('executive-assistant');
57
+ expect(config.spec.skills).toHaveLength(3);
58
+ });
59
+
60
+ it('CalendarSkill matches scheduling', async () => {
61
+ const skill = new CalendarSkill();
62
+ const r = await skill.execute(ctx, msg('Schedule a meeting'));
63
+ expect(r.handled).toBe(true);
64
+ });
65
+
66
+ it('EmailDraftSkill matches email queries', async () => {
67
+ const skill = new EmailDraftSkill();
68
+ const r = await skill.execute(ctx, msg('Draft an email'));
69
+ expect(r.handled).toBe(true);
70
+ });
71
+
72
+ it('MeetingPrepSkill matches prep queries', async () => {
73
+ const skill = new MeetingPrepSkill();
74
+ const r = await skill.execute(ctx, msg('Prepare the agenda'));
75
+ expect(r.handled).toBe(true);
76
+ });
77
+ });