opc-agent 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +20 -20
- package/.github/ISSUE_TEMPLATE/feature_request.md +14 -14
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -13
- package/CHANGELOG.md +48 -48
- package/CONTRIBUTING.md +36 -36
- package/README.zh-CN.md +497 -497
- package/USABILITY-ISSUES.md +73 -0
- package/dist/channels/web.js +8 -2
- package/dist/channels/wechat.js +6 -6
- package/dist/cli.js +200 -85
- package/dist/core/runtime.js +37 -15
- package/dist/deploy/index.js +56 -56
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +105 -10
- package/dist/memory/deepbrain.d.ts +1 -1
- package/dist/memory/deepbrain.js +95 -4
- package/dist/scheduler/cron-engine.js +3 -36
- package/dist/studio/server.js +30 -1
- package/dist/studio-ui/index.html +230 -10
- package/dist/ui/components.js +105 -105
- package/examples/README.md +22 -22
- package/examples/basic-agent.ts +90 -90
- package/examples/brain-integration.ts +71 -71
- package/examples/multi-channel.ts +74 -74
- package/fix-sidebar.mjs +188 -188
- package/install.ps1 +154 -154
- package/install.sh +164 -164
- package/package.json +1 -1
- package/scripts/install.ps1 +31 -31
- package/scripts/install.sh +40 -40
- package/serve-studio.js +13 -13
- package/serve-test.js +25 -25
- package/src/channels/dingtalk.ts +46 -46
- package/src/channels/email.ts +351 -351
- package/src/channels/feishu.ts +349 -349
- package/src/channels/googlechat.ts +42 -42
- package/src/channels/imessage.ts +31 -31
- package/src/channels/irc.ts +82 -82
- package/src/channels/line.ts +32 -32
- package/src/channels/matrix.ts +33 -33
- package/src/channels/mattermost.ts +57 -57
- package/src/channels/msteams.ts +32 -32
- package/src/channels/nostr.ts +32 -32
- package/src/channels/qq.ts +33 -33
- package/src/channels/signal.ts +32 -32
- package/src/channels/sms.ts +33 -33
- package/src/channels/telegram.ts +616 -616
- package/src/channels/twitch.ts +65 -65
- package/src/channels/voice-call.ts +100 -100
- package/src/channels/web.ts +8 -2
- package/src/channels/websocket.ts +399 -399
- package/src/channels/wechat.ts +329 -329
- package/src/channels/whatsapp.ts +32 -32
- package/src/cli/chat.ts +99 -99
- package/src/cli/setup.ts +314 -314
- package/src/cli.ts +195 -92
- package/src/core/agent.ts +476 -476
- package/src/core/api-server.ts +277 -277
- package/src/core/audio.ts +98 -98
- package/src/core/collaboration.ts +275 -275
- package/src/core/context-discovery.ts +85 -85
- package/src/core/context-refs.ts +140 -140
- package/src/core/gateway.ts +106 -106
- package/src/core/heartbeat.ts +51 -51
- package/src/core/hooks.ts +105 -105
- package/src/core/ide-bridge.ts +133 -133
- package/src/core/node-network.ts +86 -86
- package/src/core/profiles.ts +122 -122
- package/src/core/runtime.ts +25 -0
- package/src/core/scheduler.ts +187 -187
- package/src/core/session-manager.ts +137 -137
- package/src/core/subagent.ts +98 -98
- package/src/core/vision.ts +180 -180
- package/src/core/workflow-graph.ts +365 -365
- package/src/daemon.ts +96 -96
- package/src/deploy/index.ts +255 -255
- package/src/doctor.ts +98 -11
- package/src/eval/index.ts +211 -211
- package/src/eval/suites/basic.json +16 -16
- package/src/eval/suites/memory.json +12 -12
- package/src/eval/suites/safety.json +14 -14
- package/src/hub/brain-seed.ts +54 -54
- package/src/hub/client.ts +60 -60
- package/src/mcp/servers/calculator-mcp.ts +65 -65
- package/src/mcp/servers/crypto-mcp.ts +73 -73
- package/src/mcp/servers/database-mcp.ts +72 -72
- package/src/mcp/servers/datetime-mcp.ts +69 -69
- package/src/mcp/servers/filesystem.ts +66 -66
- package/src/mcp/servers/github-mcp.ts +58 -58
- package/src/mcp/servers/index.ts +63 -63
- package/src/mcp/servers/json-mcp.ts +102 -102
- package/src/mcp/servers/memory-mcp.ts +56 -56
- package/src/mcp/servers/regex-mcp.ts +53 -53
- package/src/mcp/servers/web-mcp.ts +49 -49
- package/src/memory/context-compressor.ts +189 -189
- package/src/memory/deepbrain.ts +99 -5
- package/src/memory/seed-loader.ts +212 -212
- package/src/memory/user-profiler.ts +215 -215
- package/src/plugins/content-filter.ts +23 -23
- package/src/plugins/logger.ts +18 -18
- package/src/plugins/rate-limiter.ts +38 -38
- package/src/protocols/a2a/client.ts +132 -132
- package/src/protocols/a2a/index.ts +8 -8
- package/src/protocols/a2a/server.ts +333 -333
- package/src/protocols/a2a/types.ts +88 -88
- package/src/protocols/a2a/utils.ts +50 -50
- package/src/protocols/agui/client.ts +83 -83
- package/src/protocols/agui/index.ts +4 -4
- package/src/protocols/agui/server.ts +218 -218
- package/src/protocols/agui/types.ts +153 -153
- package/src/protocols/index.ts +2 -2
- package/src/protocols/mcp/agent-tools.ts +134 -134
- package/src/protocols/mcp/index.ts +8 -8
- package/src/protocols/mcp/server.ts +262 -262
- package/src/protocols/mcp/types.ts +69 -69
- package/src/providers/index.ts +632 -632
- package/src/publish/index.ts +376 -376
- package/src/scheduler/cron-engine.ts +191 -191
- package/src/scheduler/index.ts +2 -2
- package/src/schema/oad.ts +217 -217
- package/src/security/approval.ts +131 -131
- package/src/security/approvals.ts +143 -143
- package/src/security/elevated.ts +105 -105
- package/src/security/guardrails.ts +248 -248
- package/src/security/index.ts +9 -9
- package/src/security/keys.ts +87 -87
- package/src/security/secrets.ts +129 -129
- package/src/skills/builtin/index.ts +408 -408
- package/src/skills/marketplace.ts +113 -113
- package/src/skills/types.ts +42 -42
- package/src/studio/server.ts +31 -1
- package/src/studio/templates-data.ts +178 -178
- package/src/studio-ui/index.html +230 -10
- package/src/telemetry/index.ts +324 -324
- package/src/tools/builtin/browser.ts +299 -299
- package/src/tools/builtin/datetime.ts +41 -41
- package/src/tools/builtin/file.ts +107 -107
- package/src/tools/builtin/home-assistant.ts +116 -116
- package/src/tools/builtin/rl-tools.ts +243 -243
- package/src/tools/builtin/shell.ts +43 -43
- package/src/tools/builtin/vision.ts +64 -64
- package/src/tools/builtin/web-search.ts +126 -126
- package/src/tools/builtin/web.ts +35 -35
- package/src/tools/document-processor.ts +213 -213
- package/src/tools/image-generator.ts +150 -150
- package/src/tools/integrations/calendar.ts +73 -73
- package/src/tools/integrations/code-exec.ts +39 -39
- package/src/tools/integrations/csv-analyzer.ts +92 -92
- package/src/tools/integrations/database.ts +44 -44
- package/src/tools/integrations/email-send.ts +76 -76
- package/src/tools/integrations/git-tool.ts +42 -42
- package/src/tools/integrations/github-tool.ts +76 -76
- package/src/tools/integrations/image-gen.ts +56 -56
- package/src/tools/integrations/index.ts +92 -92
- package/src/tools/integrations/jira.ts +83 -83
- package/src/tools/integrations/notion.ts +71 -71
- package/src/tools/integrations/npm-tool.ts +48 -48
- package/src/tools/integrations/pdf-reader.ts +58 -58
- package/src/tools/integrations/slack.ts +65 -65
- package/src/tools/integrations/summarizer.ts +49 -49
- package/src/tools/integrations/translator.ts +48 -48
- package/src/tools/integrations/trello.ts +60 -60
- package/src/tools/integrations/vector-search.ts +42 -42
- package/src/tools/integrations/web-scraper.ts +47 -47
- package/src/tools/integrations/web-search.ts +58 -58
- package/src/tools/integrations/webhook.ts +38 -38
- package/src/tools/mcp-client.ts +131 -131
- package/src/tools/web-scraper.ts +179 -179
- package/src/tools/web-search.ts +180 -180
- package/src/ui/components.ts +127 -127
- package/srv-out.txt +1 -1
- package/templates/ecommerce-assistant/README.md +45 -45
- package/templates/ecommerce-assistant/oad.yaml +47 -47
- package/templates/tech-support/README.md +43 -43
- package/templates/tech-support/oad.yaml +45 -45
- package/test-agent/Dockerfile +9 -9
- package/test-agent/README.md +50 -50
- package/test-agent/agent.yaml +23 -23
- package/test-agent/docker-compose.yml +11 -11
- package/test-agent/oad.yaml +31 -31
- package/test-agent/package-lock.json +1492 -1492
- package/test-agent/package.json +17 -17
- package/test-agent/src/index.ts +24 -24
- package/test-agent/src/skills/echo.ts +15 -15
- package/test-agent/tsconfig.json +24 -24
- package/test-full.js +43 -43
- package/test-sidebar.js +22 -22
- package/test-studio3.js +75 -75
- package/test-studio4.js +41 -41
- package/tests/a2a-protocol.test.ts +285 -285
- package/tests/agui-protocol.test.ts +246 -246
- package/tests/api-server.test.ts +148 -148
- package/tests/approvals.test.ts +89 -89
- package/tests/audio.test.ts +40 -40
- package/tests/brain-seed-extended.test.ts +490 -490
- package/tests/brain-seed.test.ts +239 -239
- package/tests/browser.test.ts +179 -179
- package/tests/channels/discord.test.ts +79 -79
- package/tests/channels/email.test.ts +148 -148
- package/tests/channels/feishu.test.ts +123 -123
- package/tests/channels/telegram.test.ts +129 -129
- package/tests/channels/websocket.test.ts +53 -53
- package/tests/channels/wechat.test.ts +170 -170
- package/tests/channels-extra.test.ts +45 -45
- package/tests/chat-cli.test.ts +160 -160
- package/tests/cli.test.ts +46 -46
- package/tests/context-compressor.test.ts +172 -172
- package/tests/context-refs.test.ts +121 -121
- package/tests/cron-engine.test.ts +101 -101
- package/tests/daemon.test.ts +135 -135
- package/tests/deepbrain-wire.test.ts +234 -234
- package/tests/deploy-and-dag.test.ts +196 -196
- package/tests/doctor.test.ts +38 -38
- package/tests/document-processor.test.ts +69 -69
- package/tests/e2e-nocode.test.ts +442 -442
- package/tests/elevated.test.ts +69 -69
- package/tests/eval.test.ts +173 -173
- package/tests/gateway.test.ts +63 -63
- package/tests/guardrails.test.ts +177 -177
- package/tests/home-assistant.test.ts +40 -40
- package/tests/hooks.test.ts +79 -79
- package/tests/ide-bridge.test.ts +38 -38
- package/tests/image-generator.test.ts +84 -84
- package/tests/init-role.test.ts +124 -124
- package/tests/integrations.test.ts +249 -249
- package/tests/mcp-client.test.ts +92 -92
- package/tests/mcp-server.test.ts +178 -178
- package/tests/mcp-servers.test.ts +260 -260
- package/tests/node-network.test.ts +74 -74
- package/tests/plugin-a2a-enhanced.test.ts +230 -230
- package/tests/profiles.test.ts +61 -61
- package/tests/publish.test.ts +231 -231
- package/tests/rl-tools.test.ts +93 -93
- package/tests/sandbox-manager.test.ts +46 -46
- package/tests/scheduler.test.ts +200 -200
- package/tests/secrets.test.ts +107 -107
- package/tests/security-enhanced.test.ts +233 -233
- package/tests/settings-api.test.ts +148 -148
- package/tests/setup.test.ts +73 -73
- package/tests/subagent.test.ts +193 -193
- package/tests/telegram-discord.test.ts +60 -60
- package/tests/telemetry.test.ts +186 -186
- package/tests/user-profiler.test.ts +169 -169
- package/tests/v090-features.test.ts +254 -254
- package/tests/vision.test.ts +61 -61
- package/tests/voice-call.test.ts +47 -47
- package/tests/voice-enhanced.test.ts +169 -169
- package/tests/voice-interaction.test.ts +38 -38
- package/tests/web-search.test.ts +155 -155
- package/tests/workflow-graph.test.ts +279 -279
- package/tutorial/customer-service-agent/README.md +612 -612
- package/tutorial/customer-service-agent/SOUL.md +26 -26
- package/tutorial/customer-service-agent/agent.yaml +63 -63
- package/tutorial/customer-service-agent/package.json +19 -19
- package/tutorial/customer-service-agent/src/index.ts +69 -69
- package/tutorial/customer-service-agent/src/skills/faq.ts +27 -27
- package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -22
- package/tutorial/customer-service-agent/tsconfig.json +14 -14
package/tests/profiles.test.ts
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { ProfileManager } from '../src/core/profiles';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import * as os from 'os';
|
|
6
|
-
|
|
7
|
-
describe('ProfileManager', () => {
|
|
8
|
-
let tmpDir: string;
|
|
9
|
-
let pm: ProfileManager;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
tmpDir = path.join(os.tmpdir(), `opc-profiles-test-${Date.now()}`);
|
|
13
|
-
pm = new ProfileManager(tmpDir);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should create a profile', () => {
|
|
21
|
-
const p = pm.create('test-profile', { model: 'gpt-4o' });
|
|
22
|
-
expect(p.name).toBe('test-profile');
|
|
23
|
-
expect(p.config.model).toBe('gpt-4o');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should throw on duplicate create', () => {
|
|
27
|
-
pm.create('dup');
|
|
28
|
-
expect(() => pm.create('dup')).toThrow('already exists');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should list profiles', () => {
|
|
32
|
-
pm.create('a');
|
|
33
|
-
pm.create('b');
|
|
34
|
-
const list = pm.list();
|
|
35
|
-
expect(list).toHaveLength(2);
|
|
36
|
-
expect(list.map(p => p.name).sort()).toEqual(['a', 'b']);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should switch profiles', () => {
|
|
40
|
-
pm.create('prof1');
|
|
41
|
-
pm.create('prof2');
|
|
42
|
-
pm.switch('prof1');
|
|
43
|
-
expect(pm.current().name).toBe('prof1');
|
|
44
|
-
pm.switch('prof2');
|
|
45
|
-
expect(pm.current().name).toBe('prof2');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should delete a non-current profile', () => {
|
|
49
|
-
pm.create('keeper');
|
|
50
|
-
pm.create('goner');
|
|
51
|
-
pm.switch('keeper');
|
|
52
|
-
pm.delete('goner');
|
|
53
|
-
expect(pm.list()).toHaveLength(1);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should throw when deleting current profile', () => {
|
|
57
|
-
pm.create('active');
|
|
58
|
-
pm.switch('active');
|
|
59
|
-
expect(() => pm.delete('active')).toThrow('Cannot delete');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { ProfileManager } from '../src/core/profiles';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as os from 'os';
|
|
6
|
+
|
|
7
|
+
describe('ProfileManager', () => {
|
|
8
|
+
let tmpDir: string;
|
|
9
|
+
let pm: ProfileManager;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tmpDir = path.join(os.tmpdir(), `opc-profiles-test-${Date.now()}`);
|
|
13
|
+
pm = new ProfileManager(tmpDir);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create a profile', () => {
|
|
21
|
+
const p = pm.create('test-profile', { model: 'gpt-4o' });
|
|
22
|
+
expect(p.name).toBe('test-profile');
|
|
23
|
+
expect(p.config.model).toBe('gpt-4o');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should throw on duplicate create', () => {
|
|
27
|
+
pm.create('dup');
|
|
28
|
+
expect(() => pm.create('dup')).toThrow('already exists');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should list profiles', () => {
|
|
32
|
+
pm.create('a');
|
|
33
|
+
pm.create('b');
|
|
34
|
+
const list = pm.list();
|
|
35
|
+
expect(list).toHaveLength(2);
|
|
36
|
+
expect(list.map(p => p.name).sort()).toEqual(['a', 'b']);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should switch profiles', () => {
|
|
40
|
+
pm.create('prof1');
|
|
41
|
+
pm.create('prof2');
|
|
42
|
+
pm.switch('prof1');
|
|
43
|
+
expect(pm.current().name).toBe('prof1');
|
|
44
|
+
pm.switch('prof2');
|
|
45
|
+
expect(pm.current().name).toBe('prof2');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should delete a non-current profile', () => {
|
|
49
|
+
pm.create('keeper');
|
|
50
|
+
pm.create('goner');
|
|
51
|
+
pm.switch('keeper');
|
|
52
|
+
pm.delete('goner');
|
|
53
|
+
expect(pm.list()).toHaveLength(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should throw when deleting current profile', () => {
|
|
57
|
+
pm.create('active');
|
|
58
|
+
pm.switch('active');
|
|
59
|
+
expect(() => pm.delete('active')).toThrow('Cannot delete');
|
|
60
|
+
});
|
|
61
|
+
});
|
package/tests/publish.test.ts
CHANGED
|
@@ -1,231 +1,231 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
import * as crypto from 'crypto';
|
|
6
|
-
import { AgentPackager, AgentPublisher, AgentInstaller } from '../src/publish';
|
|
7
|
-
|
|
8
|
-
function makeTempDir(): string {
|
|
9
|
-
const dir = path.join(os.tmpdir(), `opc-test-${crypto.randomBytes(6).toString('hex')}`);
|
|
10
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
-
return dir;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function writeAgentYaml(dir: string, overrides: Record<string, any> = {}) {
|
|
15
|
-
const config = {
|
|
16
|
-
apiVersion: 'opc/v1',
|
|
17
|
-
kind: 'Agent',
|
|
18
|
-
metadata: { name: 'test-agent', version: '1.0.0', description: 'Test agent', author: 'Test', license: 'MIT', ...overrides.metadata },
|
|
19
|
-
spec: { model: 'gpt-4', provider: { default: 'openai' }, channels: [{ type: 'web' }], skills: [{ name: 'echo' }], tools: [], ...overrides.spec },
|
|
20
|
-
};
|
|
21
|
-
const yaml = require('js-yaml');
|
|
22
|
-
fs.writeFileSync(path.join(dir, 'agent.yaml'), yaml.dump(config));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function writePackageJson(dir: string, overrides: Record<string, any> = {}) {
|
|
26
|
-
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'test-agent', version: '1.0.0', ...overrides }, null, 2));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function setupValidProject(dir: string) {
|
|
30
|
-
writeAgentYaml(dir);
|
|
31
|
-
writePackageJson(dir);
|
|
32
|
-
fs.writeFileSync(path.join(dir, 'SOUL.md'), '# Soul');
|
|
33
|
-
fs.writeFileSync(path.join(dir, 'README.md'), '# Readme');
|
|
34
|
-
fs.writeFileSync(path.join(dir, 'src.ts'), 'console.log("hello")');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
describe('AgentPackager', () => {
|
|
38
|
-
let tmpDir: string;
|
|
39
|
-
const packager = new AgentPackager();
|
|
40
|
-
|
|
41
|
-
beforeEach(() => { tmpDir = makeTempDir(); });
|
|
42
|
-
afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); });
|
|
43
|
-
|
|
44
|
-
// 1. validate: missing agent.yaml → error
|
|
45
|
-
it('validate: missing agent.yaml produces error', async () => {
|
|
46
|
-
writePackageJson(tmpDir);
|
|
47
|
-
const result = await packager.validate(tmpDir);
|
|
48
|
-
expect(result.valid).toBe(false);
|
|
49
|
-
expect(result.errors).toContain('Missing agent.yaml');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// 2. validate: missing package.json → error
|
|
53
|
-
it('validate: missing package.json produces error', async () => {
|
|
54
|
-
writeAgentYaml(tmpDir);
|
|
55
|
-
const result = await packager.validate(tmpDir);
|
|
56
|
-
expect(result.valid).toBe(false);
|
|
57
|
-
expect(result.errors).toContain('Missing package.json');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// 3. validate: valid project → no errors
|
|
61
|
-
it('validate: valid project has no errors', async () => {
|
|
62
|
-
setupValidProject(tmpDir);
|
|
63
|
-
const result = await packager.validate(tmpDir);
|
|
64
|
-
expect(result.valid).toBe(true);
|
|
65
|
-
expect(result.errors).toHaveLength(0);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// 4. validate: missing SOUL.md → warning
|
|
69
|
-
it('validate: missing SOUL.md produces warning', async () => {
|
|
70
|
-
writeAgentYaml(tmpDir);
|
|
71
|
-
writePackageJson(tmpDir);
|
|
72
|
-
const result = await packager.validate(tmpDir);
|
|
73
|
-
expect(result.warnings).toContain('Missing SOUL.md (recommended)');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// 5. validate: missing README.md → warning
|
|
77
|
-
it('validate: missing README.md produces warning', async () => {
|
|
78
|
-
writeAgentYaml(tmpDir);
|
|
79
|
-
writePackageJson(tmpDir);
|
|
80
|
-
const result = await packager.validate(tmpDir);
|
|
81
|
-
expect(result.warnings).toContain('Missing README.md');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// 6. validate: uppercase name → error
|
|
85
|
-
it('validate: uppercase name in agent.yaml produces error', async () => {
|
|
86
|
-
writeAgentYaml(tmpDir, { metadata: { name: 'MyAgent', version: '1.0.0' } });
|
|
87
|
-
writePackageJson(tmpDir);
|
|
88
|
-
const result = await packager.validate(tmpDir);
|
|
89
|
-
expect(result.valid).toBe(false);
|
|
90
|
-
expect(result.errors.some(e => e.includes('lowercase'))).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// 7. validate: invalid version → error
|
|
94
|
-
it('validate: invalid version format produces error', async () => {
|
|
95
|
-
writeAgentYaml(tmpDir, { metadata: { name: 'test', version: 'bad' } });
|
|
96
|
-
writePackageJson(tmpDir);
|
|
97
|
-
const result = await packager.validate(tmpDir);
|
|
98
|
-
expect(result.valid).toBe(false);
|
|
99
|
-
expect(result.errors.some(e => e.includes('version'))).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// 8. listFiles: excludes node_modules
|
|
103
|
-
it('listFiles: excludes node_modules', async () => {
|
|
104
|
-
setupValidProject(tmpDir);
|
|
105
|
-
fs.mkdirSync(path.join(tmpDir, 'node_modules', 'foo'), { recursive: true });
|
|
106
|
-
fs.writeFileSync(path.join(tmpDir, 'node_modules', 'foo', 'index.js'), '');
|
|
107
|
-
const files = await packager.listFiles(tmpDir);
|
|
108
|
-
expect(files.every(f => !f.includes('node_modules'))).toBe(true);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// 9. listFiles: excludes .env
|
|
112
|
-
it('listFiles: excludes .env', async () => {
|
|
113
|
-
setupValidProject(tmpDir);
|
|
114
|
-
fs.writeFileSync(path.join(tmpDir, '.env'), 'SECRET=x');
|
|
115
|
-
const files = await packager.listFiles(tmpDir);
|
|
116
|
-
expect(files).not.toContain('.env');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// 10. listFiles: excludes .git
|
|
120
|
-
it('listFiles: excludes .git directory', async () => {
|
|
121
|
-
setupValidProject(tmpDir);
|
|
122
|
-
fs.mkdirSync(path.join(tmpDir, '.git'), { recursive: true });
|
|
123
|
-
fs.writeFileSync(path.join(tmpDir, '.git', 'config'), '');
|
|
124
|
-
const files = await packager.listFiles(tmpDir);
|
|
125
|
-
expect(files.every(f => !f.includes('.git'))).toBe(true);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// 11. listFiles: respects .opcignore
|
|
129
|
-
it('listFiles: respects .opcignore', async () => {
|
|
130
|
-
setupValidProject(tmpDir);
|
|
131
|
-
fs.writeFileSync(path.join(tmpDir, 'secret.txt'), 'hidden');
|
|
132
|
-
fs.writeFileSync(path.join(tmpDir, '.opcignore'), 'secret.txt\n');
|
|
133
|
-
const files = await packager.listFiles(tmpDir);
|
|
134
|
-
expect(files).not.toContain('secret.txt');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// 12. pack: creates .opc.tgz file
|
|
138
|
-
it('pack: creates .opc.tgz file', async () => {
|
|
139
|
-
setupValidProject(tmpDir);
|
|
140
|
-
const result = await packager.pack(tmpDir);
|
|
141
|
-
expect(fs.existsSync(result.path)).toBe(true);
|
|
142
|
-
expect(result.path).toMatch(/\.opc\.tgz$/);
|
|
143
|
-
// cleanup
|
|
144
|
-
fs.unlinkSync(result.path);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// 13. pack: manifest contains correct fields
|
|
148
|
-
it('pack: manifest contains correct fields', async () => {
|
|
149
|
-
setupValidProject(tmpDir);
|
|
150
|
-
const result = await packager.pack(tmpDir);
|
|
151
|
-
expect(result.manifest.name).toBe('test-agent');
|
|
152
|
-
expect(result.manifest.version).toBe('1.0.0');
|
|
153
|
-
expect(result.manifest.author).toBe('Test');
|
|
154
|
-
expect(result.manifest.license).toBe('MIT');
|
|
155
|
-
expect(result.manifest.agent.model).toBe('gpt-4');
|
|
156
|
-
expect(result.manifest.agent.provider).toBe('openai');
|
|
157
|
-
expect(result.manifest.agent.channels).toContain('web');
|
|
158
|
-
expect(result.manifest.files.length).toBeGreaterThan(0);
|
|
159
|
-
expect(result.manifest.checksum).toBeTruthy();
|
|
160
|
-
expect(result.manifest.createdAt).toBeTruthy();
|
|
161
|
-
fs.unlinkSync(result.path);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// 14. manifest checksum is valid sha256
|
|
165
|
-
it('pack: checksum is valid sha256', async () => {
|
|
166
|
-
setupValidProject(tmpDir);
|
|
167
|
-
const result = await packager.pack(tmpDir);
|
|
168
|
-
expect(result.manifest.checksum).toMatch(/^[a-f0-9]{64}$/);
|
|
169
|
-
// Verify checksum matches file
|
|
170
|
-
const fileHash = crypto.createHash('sha256').update(fs.readFileSync(result.path)).digest('hex');
|
|
171
|
-
expect(result.manifest.checksum).toBe(fileHash);
|
|
172
|
-
fs.unlinkSync(result.path);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// 15. pack: validation failure throws
|
|
176
|
-
it('pack: throws on invalid project', async () => {
|
|
177
|
-
// Empty dir — no agent.yaml
|
|
178
|
-
await expect(packager.pack(tmpDir)).rejects.toThrow('Validation failed');
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe('AgentPublisher', () => {
|
|
183
|
-
// 16. dry-run doesn't throw
|
|
184
|
-
it('dry-run returns success without error', async () => {
|
|
185
|
-
const publisher = new AgentPublisher();
|
|
186
|
-
const manifest = {
|
|
187
|
-
name: 'test', version: '1.0.0', description: '', author: '', license: 'MIT',
|
|
188
|
-
agent: { model: '', provider: '', channels: [], skills: [], tools: [] },
|
|
189
|
-
files: ['a.ts'], checksum: 'abc', createdAt: new Date().toISOString(),
|
|
190
|
-
};
|
|
191
|
-
const result = await publisher.publish('/fake/path.opc.tgz', manifest, { dryRun: true });
|
|
192
|
-
expect(result.success).toBe(true);
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
describe('AgentInstaller', () => {
|
|
197
|
-
let tmpDir: string;
|
|
198
|
-
let installDir: string;
|
|
199
|
-
|
|
200
|
-
beforeEach(() => {
|
|
201
|
-
tmpDir = makeTempDir();
|
|
202
|
-
installDir = makeTempDir();
|
|
203
|
-
});
|
|
204
|
-
afterEach(() => {
|
|
205
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
206
|
-
fs.rmSync(installDir, { recursive: true, force: true });
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// 17. install from tarball extracts correctly
|
|
210
|
-
it('install from .opc.tgz extracts files', async () => {
|
|
211
|
-
// Create a valid project and pack it
|
|
212
|
-
setupValidProject(tmpDir);
|
|
213
|
-
const packager = new AgentPackager();
|
|
214
|
-
const { path: pkgPath } = await packager.pack(tmpDir);
|
|
215
|
-
|
|
216
|
-
const installer = new AgentInstaller();
|
|
217
|
-
await installer.install(pkgPath, installDir);
|
|
218
|
-
|
|
219
|
-
// Check that files were extracted
|
|
220
|
-
const extractedFiles = fs.readdirSync(installDir);
|
|
221
|
-
expect(extractedFiles.length).toBeGreaterThan(0);
|
|
222
|
-
|
|
223
|
-
fs.unlinkSync(pkgPath);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// 18. install from missing file throws
|
|
227
|
-
it('install from missing file throws', async () => {
|
|
228
|
-
const installer = new AgentInstaller();
|
|
229
|
-
await expect(installer.install('/nonexistent/pkg.opc.tgz', installDir)).rejects.toThrow();
|
|
230
|
-
});
|
|
231
|
-
});
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as crypto from 'crypto';
|
|
6
|
+
import { AgentPackager, AgentPublisher, AgentInstaller } from '../src/publish';
|
|
7
|
+
|
|
8
|
+
function makeTempDir(): string {
|
|
9
|
+
const dir = path.join(os.tmpdir(), `opc-test-${crypto.randomBytes(6).toString('hex')}`);
|
|
10
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function writeAgentYaml(dir: string, overrides: Record<string, any> = {}) {
|
|
15
|
+
const config = {
|
|
16
|
+
apiVersion: 'opc/v1',
|
|
17
|
+
kind: 'Agent',
|
|
18
|
+
metadata: { name: 'test-agent', version: '1.0.0', description: 'Test agent', author: 'Test', license: 'MIT', ...overrides.metadata },
|
|
19
|
+
spec: { model: 'gpt-4', provider: { default: 'openai' }, channels: [{ type: 'web' }], skills: [{ name: 'echo' }], tools: [], ...overrides.spec },
|
|
20
|
+
};
|
|
21
|
+
const yaml = require('js-yaml');
|
|
22
|
+
fs.writeFileSync(path.join(dir, 'agent.yaml'), yaml.dump(config));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writePackageJson(dir: string, overrides: Record<string, any> = {}) {
|
|
26
|
+
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'test-agent', version: '1.0.0', ...overrides }, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function setupValidProject(dir: string) {
|
|
30
|
+
writeAgentYaml(dir);
|
|
31
|
+
writePackageJson(dir);
|
|
32
|
+
fs.writeFileSync(path.join(dir, 'SOUL.md'), '# Soul');
|
|
33
|
+
fs.writeFileSync(path.join(dir, 'README.md'), '# Readme');
|
|
34
|
+
fs.writeFileSync(path.join(dir, 'src.ts'), 'console.log("hello")');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('AgentPackager', () => {
|
|
38
|
+
let tmpDir: string;
|
|
39
|
+
const packager = new AgentPackager();
|
|
40
|
+
|
|
41
|
+
beforeEach(() => { tmpDir = makeTempDir(); });
|
|
42
|
+
afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); });
|
|
43
|
+
|
|
44
|
+
// 1. validate: missing agent.yaml → error
|
|
45
|
+
it('validate: missing agent.yaml produces error', async () => {
|
|
46
|
+
writePackageJson(tmpDir);
|
|
47
|
+
const result = await packager.validate(tmpDir);
|
|
48
|
+
expect(result.valid).toBe(false);
|
|
49
|
+
expect(result.errors).toContain('Missing agent.yaml');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// 2. validate: missing package.json → error
|
|
53
|
+
it('validate: missing package.json produces error', async () => {
|
|
54
|
+
writeAgentYaml(tmpDir);
|
|
55
|
+
const result = await packager.validate(tmpDir);
|
|
56
|
+
expect(result.valid).toBe(false);
|
|
57
|
+
expect(result.errors).toContain('Missing package.json');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 3. validate: valid project → no errors
|
|
61
|
+
it('validate: valid project has no errors', async () => {
|
|
62
|
+
setupValidProject(tmpDir);
|
|
63
|
+
const result = await packager.validate(tmpDir);
|
|
64
|
+
expect(result.valid).toBe(true);
|
|
65
|
+
expect(result.errors).toHaveLength(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// 4. validate: missing SOUL.md → warning
|
|
69
|
+
it('validate: missing SOUL.md produces warning', async () => {
|
|
70
|
+
writeAgentYaml(tmpDir);
|
|
71
|
+
writePackageJson(tmpDir);
|
|
72
|
+
const result = await packager.validate(tmpDir);
|
|
73
|
+
expect(result.warnings).toContain('Missing SOUL.md (recommended)');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 5. validate: missing README.md → warning
|
|
77
|
+
it('validate: missing README.md produces warning', async () => {
|
|
78
|
+
writeAgentYaml(tmpDir);
|
|
79
|
+
writePackageJson(tmpDir);
|
|
80
|
+
const result = await packager.validate(tmpDir);
|
|
81
|
+
expect(result.warnings).toContain('Missing README.md');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 6. validate: uppercase name → error
|
|
85
|
+
it('validate: uppercase name in agent.yaml produces error', async () => {
|
|
86
|
+
writeAgentYaml(tmpDir, { metadata: { name: 'MyAgent', version: '1.0.0' } });
|
|
87
|
+
writePackageJson(tmpDir);
|
|
88
|
+
const result = await packager.validate(tmpDir);
|
|
89
|
+
expect(result.valid).toBe(false);
|
|
90
|
+
expect(result.errors.some(e => e.includes('lowercase'))).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// 7. validate: invalid version → error
|
|
94
|
+
it('validate: invalid version format produces error', async () => {
|
|
95
|
+
writeAgentYaml(tmpDir, { metadata: { name: 'test', version: 'bad' } });
|
|
96
|
+
writePackageJson(tmpDir);
|
|
97
|
+
const result = await packager.validate(tmpDir);
|
|
98
|
+
expect(result.valid).toBe(false);
|
|
99
|
+
expect(result.errors.some(e => e.includes('version'))).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 8. listFiles: excludes node_modules
|
|
103
|
+
it('listFiles: excludes node_modules', async () => {
|
|
104
|
+
setupValidProject(tmpDir);
|
|
105
|
+
fs.mkdirSync(path.join(tmpDir, 'node_modules', 'foo'), { recursive: true });
|
|
106
|
+
fs.writeFileSync(path.join(tmpDir, 'node_modules', 'foo', 'index.js'), '');
|
|
107
|
+
const files = await packager.listFiles(tmpDir);
|
|
108
|
+
expect(files.every(f => !f.includes('node_modules'))).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// 9. listFiles: excludes .env
|
|
112
|
+
it('listFiles: excludes .env', async () => {
|
|
113
|
+
setupValidProject(tmpDir);
|
|
114
|
+
fs.writeFileSync(path.join(tmpDir, '.env'), 'SECRET=x');
|
|
115
|
+
const files = await packager.listFiles(tmpDir);
|
|
116
|
+
expect(files).not.toContain('.env');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// 10. listFiles: excludes .git
|
|
120
|
+
it('listFiles: excludes .git directory', async () => {
|
|
121
|
+
setupValidProject(tmpDir);
|
|
122
|
+
fs.mkdirSync(path.join(tmpDir, '.git'), { recursive: true });
|
|
123
|
+
fs.writeFileSync(path.join(tmpDir, '.git', 'config'), '');
|
|
124
|
+
const files = await packager.listFiles(tmpDir);
|
|
125
|
+
expect(files.every(f => !f.includes('.git'))).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// 11. listFiles: respects .opcignore
|
|
129
|
+
it('listFiles: respects .opcignore', async () => {
|
|
130
|
+
setupValidProject(tmpDir);
|
|
131
|
+
fs.writeFileSync(path.join(tmpDir, 'secret.txt'), 'hidden');
|
|
132
|
+
fs.writeFileSync(path.join(tmpDir, '.opcignore'), 'secret.txt\n');
|
|
133
|
+
const files = await packager.listFiles(tmpDir);
|
|
134
|
+
expect(files).not.toContain('secret.txt');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// 12. pack: creates .opc.tgz file
|
|
138
|
+
it('pack: creates .opc.tgz file', async () => {
|
|
139
|
+
setupValidProject(tmpDir);
|
|
140
|
+
const result = await packager.pack(tmpDir);
|
|
141
|
+
expect(fs.existsSync(result.path)).toBe(true);
|
|
142
|
+
expect(result.path).toMatch(/\.opc\.tgz$/);
|
|
143
|
+
// cleanup
|
|
144
|
+
fs.unlinkSync(result.path);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// 13. pack: manifest contains correct fields
|
|
148
|
+
it('pack: manifest contains correct fields', async () => {
|
|
149
|
+
setupValidProject(tmpDir);
|
|
150
|
+
const result = await packager.pack(tmpDir);
|
|
151
|
+
expect(result.manifest.name).toBe('test-agent');
|
|
152
|
+
expect(result.manifest.version).toBe('1.0.0');
|
|
153
|
+
expect(result.manifest.author).toBe('Test');
|
|
154
|
+
expect(result.manifest.license).toBe('MIT');
|
|
155
|
+
expect(result.manifest.agent.model).toBe('gpt-4');
|
|
156
|
+
expect(result.manifest.agent.provider).toBe('openai');
|
|
157
|
+
expect(result.manifest.agent.channels).toContain('web');
|
|
158
|
+
expect(result.manifest.files.length).toBeGreaterThan(0);
|
|
159
|
+
expect(result.manifest.checksum).toBeTruthy();
|
|
160
|
+
expect(result.manifest.createdAt).toBeTruthy();
|
|
161
|
+
fs.unlinkSync(result.path);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// 14. manifest checksum is valid sha256
|
|
165
|
+
it('pack: checksum is valid sha256', async () => {
|
|
166
|
+
setupValidProject(tmpDir);
|
|
167
|
+
const result = await packager.pack(tmpDir);
|
|
168
|
+
expect(result.manifest.checksum).toMatch(/^[a-f0-9]{64}$/);
|
|
169
|
+
// Verify checksum matches file
|
|
170
|
+
const fileHash = crypto.createHash('sha256').update(fs.readFileSync(result.path)).digest('hex');
|
|
171
|
+
expect(result.manifest.checksum).toBe(fileHash);
|
|
172
|
+
fs.unlinkSync(result.path);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// 15. pack: validation failure throws
|
|
176
|
+
it('pack: throws on invalid project', async () => {
|
|
177
|
+
// Empty dir — no agent.yaml
|
|
178
|
+
await expect(packager.pack(tmpDir)).rejects.toThrow('Validation failed');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('AgentPublisher', () => {
|
|
183
|
+
// 16. dry-run doesn't throw
|
|
184
|
+
it('dry-run returns success without error', async () => {
|
|
185
|
+
const publisher = new AgentPublisher();
|
|
186
|
+
const manifest = {
|
|
187
|
+
name: 'test', version: '1.0.0', description: '', author: '', license: 'MIT',
|
|
188
|
+
agent: { model: '', provider: '', channels: [], skills: [], tools: [] },
|
|
189
|
+
files: ['a.ts'], checksum: 'abc', createdAt: new Date().toISOString(),
|
|
190
|
+
};
|
|
191
|
+
const result = await publisher.publish('/fake/path.opc.tgz', manifest, { dryRun: true });
|
|
192
|
+
expect(result.success).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('AgentInstaller', () => {
|
|
197
|
+
let tmpDir: string;
|
|
198
|
+
let installDir: string;
|
|
199
|
+
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
tmpDir = makeTempDir();
|
|
202
|
+
installDir = makeTempDir();
|
|
203
|
+
});
|
|
204
|
+
afterEach(() => {
|
|
205
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
206
|
+
fs.rmSync(installDir, { recursive: true, force: true });
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// 17. install from tarball extracts correctly
|
|
210
|
+
it('install from .opc.tgz extracts files', async () => {
|
|
211
|
+
// Create a valid project and pack it
|
|
212
|
+
setupValidProject(tmpDir);
|
|
213
|
+
const packager = new AgentPackager();
|
|
214
|
+
const { path: pkgPath } = await packager.pack(tmpDir);
|
|
215
|
+
|
|
216
|
+
const installer = new AgentInstaller();
|
|
217
|
+
await installer.install(pkgPath, installDir);
|
|
218
|
+
|
|
219
|
+
// Check that files were extracted
|
|
220
|
+
const extractedFiles = fs.readdirSync(installDir);
|
|
221
|
+
expect(extractedFiles.length).toBeGreaterThan(0);
|
|
222
|
+
|
|
223
|
+
fs.unlinkSync(pkgPath);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// 18. install from missing file throws
|
|
227
|
+
it('install from missing file throws', async () => {
|
|
228
|
+
const installer = new AgentInstaller();
|
|
229
|
+
await expect(installer.install('/nonexistent/pkg.opc.tgz', installDir)).rejects.toThrow();
|
|
230
|
+
});
|
|
231
|
+
});
|