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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/.github/workflows/ci.yml +24 -0
- package/CHANGELOG.md +48 -63
- package/CONTRIBUTING.md +21 -60
- package/README.md +284 -348
- package/README.zh-CN.md +415 -415
- package/dist/channels/slack.js +93 -10
- package/dist/channels/telegram.d.ts +30 -9
- package/dist/channels/telegram.js +125 -33
- package/dist/channels/web.d.ts +10 -0
- package/dist/channels/web.js +33 -2
- package/dist/cli.js +667 -65
- package/dist/core/agent.d.ts +23 -0
- package/dist/core/agent.js +120 -3
- package/dist/core/runtime.d.ts +5 -0
- package/dist/core/runtime.js +71 -0
- package/dist/core/scheduler.d.ts +52 -0
- package/dist/core/scheduler.js +168 -0
- package/dist/core/subagent.d.ts +28 -0
- package/dist/core/subagent.js +65 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +134 -0
- package/dist/deploy/hermes.js +22 -22
- package/dist/deploy/openclaw.js +31 -40
- package/dist/index.d.ts +10 -10
- package/dist/index.js +22 -15
- package/dist/providers/index.d.ts +6 -2
- package/dist/providers/index.js +22 -9
- package/dist/schema/oad.d.ts +180 -6
- package/dist/schema/oad.js +12 -1
- package/dist/skills/auto-learn.d.ts +28 -0
- package/dist/skills/auto-learn.js +257 -0
- package/dist/templates/code-reviewer.d.ts +0 -8
- package/dist/templates/code-reviewer.js +5 -9
- package/dist/templates/customer-service.d.ts +0 -8
- package/dist/templates/customer-service.js +2 -6
- package/dist/templates/data-analyst.d.ts +0 -8
- package/dist/templates/data-analyst.js +5 -9
- package/dist/templates/knowledge-base.d.ts +0 -8
- package/dist/templates/knowledge-base.js +2 -6
- package/dist/templates/sales-assistant.d.ts +0 -8
- package/dist/templates/sales-assistant.js +4 -8
- package/dist/templates/teacher.d.ts +0 -8
- package/dist/templates/teacher.js +6 -10
- package/dist/tools/builtin/datetime.d.ts +3 -0
- package/dist/tools/builtin/datetime.js +44 -0
- package/dist/tools/builtin/file.d.ts +3 -0
- package/dist/tools/builtin/file.js +151 -0
- package/dist/tools/builtin/index.d.ts +15 -0
- package/dist/tools/builtin/index.js +30 -0
- package/dist/tools/builtin/shell.d.ts +3 -0
- package/dist/tools/builtin/shell.js +43 -0
- package/dist/tools/builtin/web.d.ts +3 -0
- package/dist/tools/builtin/web.js +37 -0
- package/dist/tools/mcp-client.d.ts +24 -0
- package/dist/tools/mcp-client.js +119 -0
- package/dist/traces/index.d.ts +49 -0
- package/dist/traces/index.js +102 -0
- package/docs/.vitepress/config.ts +103 -103
- package/docs/api/cli.md +48 -48
- package/docs/api/oad-schema.md +64 -64
- package/docs/api/sdk.md +80 -80
- package/docs/guide/concepts.md +51 -51
- package/docs/guide/configuration.md +79 -79
- package/docs/guide/deployment.md +42 -42
- package/docs/guide/getting-started.md +44 -44
- package/docs/guide/templates.md +28 -28
- package/docs/guide/testing.md +84 -84
- package/docs/index.md +27 -27
- package/docs/zh/api/cli.md +54 -54
- package/docs/zh/api/oad-schema.md +87 -87
- package/docs/zh/api/sdk.md +102 -102
- package/docs/zh/guide/concepts.md +104 -104
- package/docs/zh/guide/configuration.md +135 -135
- package/docs/zh/guide/deployment.md +81 -81
- package/docs/zh/guide/getting-started.md +82 -82
- package/docs/zh/guide/templates.md +84 -84
- package/docs/zh/guide/testing.md +88 -88
- package/docs/zh/index.md +27 -27
- package/examples/README.md +22 -0
- package/examples/basic-agent.ts +90 -0
- package/examples/brain-integration.ts +71 -0
- package/examples/customer-service-demo/README.md +90 -90
- package/examples/customer-service-demo/oad.yaml +107 -107
- package/examples/multi-channel.ts +74 -0
- package/package.json +1 -1
- package/src/analytics/index.ts +66 -66
- package/src/channels/discord.ts +192 -192
- package/src/channels/email.ts +177 -177
- package/src/channels/feishu.ts +236 -236
- package/src/channels/index.ts +15 -15
- package/src/channels/slack.ts +217 -160
- package/src/channels/telegram.ts +155 -33
- package/src/channels/voice.ts +106 -106
- package/src/channels/web.ts +38 -2
- package/src/channels/webhook.ts +199 -199
- package/src/channels/websocket.ts +87 -87
- package/src/channels/wechat.ts +149 -149
- package/src/cli.ts +697 -63
- package/src/core/a2a.ts +143 -143
- package/src/core/agent.ts +146 -3
- package/src/core/analytics-engine.ts +186 -186
- package/src/core/auth.ts +57 -57
- package/src/core/cache.ts +141 -141
- package/src/core/compose.ts +77 -77
- package/src/core/config.ts +14 -14
- package/src/core/errors.ts +148 -148
- package/src/core/hitl.ts +138 -138
- package/src/core/logger.ts +57 -57
- package/src/core/orchestrator.ts +215 -215
- package/src/core/performance.ts +187 -187
- package/src/core/rate-limiter.ts +128 -128
- package/src/core/room.ts +109 -109
- package/src/core/runtime.ts +230 -152
- package/src/core/sandbox.ts +101 -101
- package/src/core/scheduler.ts +187 -0
- package/src/core/security.ts +171 -171
- package/src/core/subagent.ts +98 -0
- package/src/core/types.ts +68 -68
- package/src/core/versioning.ts +106 -106
- package/src/core/watch.ts +178 -178
- package/src/core/workflow.ts +235 -235
- package/src/daemon.ts +96 -0
- package/src/deploy/hermes.ts +156 -156
- package/src/deploy/openclaw.ts +190 -200
- package/src/i18n/index.ts +216 -216
- package/src/index.ts +14 -10
- package/src/memory/deepbrain.ts +108 -108
- package/src/memory/index.ts +34 -34
- package/src/plugins/index.ts +208 -208
- package/src/providers/index.ts +354 -331
- package/src/schema/oad.ts +14 -2
- package/src/skills/auto-learn.ts +262 -0
- package/src/skills/base.ts +16 -16
- package/src/skills/document.ts +100 -100
- package/src/skills/http.ts +35 -35
- package/src/skills/index.ts +27 -27
- package/src/skills/scheduler.ts +80 -80
- package/src/skills/webhook-trigger.ts +59 -59
- package/src/templates/code-reviewer.ts +30 -34
- package/src/templates/customer-service.ts +76 -80
- package/src/templates/data-analyst.ts +66 -70
- package/src/templates/executive-assistant.ts +71 -71
- package/src/templates/financial-advisor.ts +60 -60
- package/src/templates/knowledge-base.ts +27 -31
- package/src/templates/legal-assistant.ts +71 -71
- package/src/templates/sales-assistant.ts +75 -79
- package/src/templates/teacher.ts +75 -79
- package/src/testing/index.ts +181 -181
- package/src/tools/builtin/datetime.ts +41 -0
- package/src/tools/builtin/file.ts +107 -0
- package/src/tools/builtin/index.ts +28 -0
- package/src/tools/builtin/shell.ts +43 -0
- package/src/tools/builtin/web.ts +35 -0
- package/src/tools/calculator.ts +73 -73
- package/src/tools/datetime.ts +149 -149
- package/src/tools/json-transform.ts +187 -187
- package/src/tools/mcp-client.ts +131 -0
- package/src/tools/mcp.ts +76 -76
- package/src/tools/text-analysis.ts +116 -116
- package/src/traces/index.ts +132 -0
- package/templates/Dockerfile +15 -15
- package/templates/code-reviewer/README.md +27 -27
- package/templates/code-reviewer/oad.yaml +41 -41
- package/templates/customer-service/README.md +22 -22
- package/templates/customer-service/oad.yaml +36 -36
- package/templates/docker-compose.yml +21 -21
- package/templates/ecommerce-assistant/README.md +45 -45
- package/templates/ecommerce-assistant/oad.yaml +47 -47
- package/templates/knowledge-base/README.md +28 -28
- package/templates/knowledge-base/oad.yaml +38 -38
- package/templates/sales-assistant/README.md +26 -26
- package/templates/sales-assistant/oad.yaml +43 -43
- package/templates/tech-support/README.md +43 -43
- package/templates/tech-support/oad.yaml +45 -45
- package/test-agent/Dockerfile +9 -0
- package/test-agent/README.md +50 -0
- package/test-agent/agent.yaml +23 -0
- package/test-agent/docker-compose.yml +11 -0
- package/test-agent/oad.yaml +31 -0
- package/test-agent/package-lock.json +1492 -0
- package/test-agent/package.json +18 -0
- package/test-agent/src/index.ts +24 -0
- package/test-agent/src/skills/echo.ts +15 -0
- package/test-agent/tsconfig.json +25 -0
- package/tests/a2a.test.ts +66 -66
- package/tests/agent.test.ts +72 -72
- package/tests/analytics.test.ts +50 -50
- package/tests/auto-learn.test.ts +105 -0
- package/tests/builtin-tools.test.ts +83 -0
- package/tests/channel.test.ts +39 -39
- package/tests/cli.test.ts +46 -0
- package/tests/e2e.test.ts +134 -134
- package/tests/errors.test.ts +83 -83
- package/tests/hitl.test.ts +71 -71
- package/tests/i18n.test.ts +41 -41
- package/tests/mcp.test.ts +54 -54
- package/tests/oad.test.ts +68 -68
- package/tests/performance.test.ts +115 -115
- package/tests/plugin.test.ts +74 -74
- package/tests/room.test.ts +106 -106
- package/tests/runtime.test.ts +42 -42
- package/tests/sandbox.test.ts +46 -46
- package/tests/security.test.ts +60 -60
- package/tests/subagent.test.ts +130 -0
- package/tests/telegram-discord.test.ts +60 -0
- package/tests/templates.test.ts +77 -77
- package/tests/v070.test.ts +76 -76
- package/tests/versioning.test.ts +75 -75
- package/tests/voice.test.ts +61 -61
- package/tests/webhook.test.ts +29 -29
- package/tests/workflow.test.ts +143 -143
- package/tsconfig.json +19 -19
- package/vitest.config.ts +9 -9
- package/dist/core/dashboard.d.ts +0 -35
- package/dist/core/dashboard.js +0 -157
- package/dist/core/priority.d.ts +0 -52
- package/dist/core/priority.js +0 -102
- package/src/core/dashboard.ts +0 -219
- package/src/core/priority.ts +0 -140
- package/src/dtv/data.ts +0 -29
- package/src/dtv/trust.ts +0 -43
- package/src/dtv/value.ts +0 -47
- package/src/marketplace/index.ts +0 -223
package/tests/runtime.test.ts
CHANGED
|
@@ -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
|
+
});
|
package/tests/sandbox.test.ts
CHANGED
|
@@ -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
|
+
});
|
package/tests/security.test.ts
CHANGED
|
@@ -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('<');
|
|
12
|
-
expect(result).toContain('&');
|
|
13
|
-
expect(result).toContain('>');
|
|
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('<');
|
|
12
|
+
expect(result).toContain('&');
|
|
13
|
+
expect(result).toContain('>');
|
|
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
|
+
});
|
package/tests/templates.test.ts
CHANGED
|
@@ -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
|
+
});
|