opc-agent 1.2.1 → 1.3.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 (152) hide show
  1. package/CONTRIBUTING.md +75 -75
  2. package/README.md +235 -358
  3. package/README.zh-CN.md +415 -415
  4. package/dist/channels/web.js +256 -256
  5. package/dist/core/knowledge.d.ts +5 -0
  6. package/dist/core/knowledge.js +39 -2
  7. package/dist/deploy/hermes.js +22 -22
  8. package/dist/deploy/openclaw.js +31 -31
  9. package/dist/index.d.ts +0 -4
  10. package/dist/index.js +1 -7
  11. package/dist/providers/index.d.ts +1 -1
  12. package/dist/providers/index.js +158 -14
  13. package/dist/schema/oad.d.ts +3 -3
  14. package/dist/templates/code-reviewer.js +5 -5
  15. package/dist/templates/customer-service.js +2 -2
  16. package/dist/templates/data-analyst.js +5 -5
  17. package/dist/templates/knowledge-base.js +2 -2
  18. package/dist/templates/sales-assistant.js +4 -4
  19. package/dist/templates/teacher.js +6 -6
  20. package/docs/.vitepress/config.ts +103 -103
  21. package/docs/api/cli.md +48 -48
  22. package/docs/api/oad-schema.md +64 -64
  23. package/docs/api/sdk.md +80 -80
  24. package/docs/guide/concepts.md +51 -51
  25. package/docs/guide/configuration.md +79 -79
  26. package/docs/guide/deployment.md +42 -42
  27. package/docs/guide/getting-started.md +44 -44
  28. package/docs/guide/templates.md +28 -28
  29. package/docs/guide/testing.md +84 -84
  30. package/docs/index.md +27 -27
  31. package/docs/zh/api/cli.md +54 -54
  32. package/docs/zh/api/oad-schema.md +87 -87
  33. package/docs/zh/api/sdk.md +102 -102
  34. package/docs/zh/guide/concepts.md +104 -104
  35. package/docs/zh/guide/configuration.md +135 -135
  36. package/docs/zh/guide/deployment.md +81 -81
  37. package/docs/zh/guide/getting-started.md +82 -82
  38. package/docs/zh/guide/templates.md +84 -84
  39. package/docs/zh/guide/testing.md +88 -88
  40. package/docs/zh/index.md +27 -27
  41. package/examples/customer-service-demo/README.md +90 -90
  42. package/examples/customer-service-demo/oad.yaml +107 -107
  43. package/package.json +50 -50
  44. package/src/analytics/index.ts +66 -66
  45. package/src/channels/discord.ts +192 -192
  46. package/src/channels/email.ts +177 -177
  47. package/src/channels/feishu.ts +236 -236
  48. package/src/channels/index.ts +15 -15
  49. package/src/channels/slack.ts +160 -160
  50. package/src/channels/telegram.ts +90 -90
  51. package/src/channels/voice.ts +106 -106
  52. package/src/channels/webhook.ts +199 -199
  53. package/src/channels/websocket.ts +87 -87
  54. package/src/channels/wechat.ts +149 -149
  55. package/src/cli.ts +119 -1
  56. package/src/core/a2a.ts +143 -143
  57. package/src/core/agent.ts +152 -152
  58. package/src/core/analytics-engine.ts +186 -186
  59. package/src/core/auth.ts +57 -57
  60. package/src/core/cache.ts +141 -141
  61. package/src/core/compose.ts +77 -77
  62. package/src/core/config.ts +14 -14
  63. package/src/core/errors.ts +148 -148
  64. package/src/core/hitl.ts +138 -138
  65. package/src/core/logger.ts +57 -57
  66. package/src/core/orchestrator.ts +215 -215
  67. package/src/core/performance.ts +187 -187
  68. package/src/core/rate-limiter.ts +128 -128
  69. package/src/core/room.ts +109 -109
  70. package/src/core/runtime.ts +152 -152
  71. package/src/core/sandbox.ts +101 -101
  72. package/src/core/security.ts +171 -171
  73. package/src/core/types.ts +68 -68
  74. package/src/core/versioning.ts +106 -106
  75. package/src/core/watch.ts +178 -178
  76. package/src/core/workflow.ts +235 -235
  77. package/src/deploy/hermes.ts +156 -156
  78. package/src/deploy/openclaw.ts +200 -200
  79. package/src/i18n/index.ts +216 -216
  80. package/src/index.ts +6 -2
  81. package/src/memory/deepbrain.ts +108 -108
  82. package/src/memory/index.ts +34 -34
  83. package/src/plugins/index.ts +208 -208
  84. package/src/schema/oad.ts +154 -155
  85. package/src/skills/base.ts +16 -16
  86. package/src/skills/document.ts +100 -100
  87. package/src/skills/http.ts +35 -35
  88. package/src/skills/index.ts +27 -27
  89. package/src/skills/scheduler.ts +80 -80
  90. package/src/skills/webhook-trigger.ts +59 -59
  91. package/src/templates/code-reviewer.ts +30 -34
  92. package/src/templates/customer-service.ts +76 -80
  93. package/src/templates/data-analyst.ts +66 -70
  94. package/src/templates/executive-assistant.ts +71 -71
  95. package/src/templates/financial-advisor.ts +60 -60
  96. package/src/templates/knowledge-base.ts +27 -31
  97. package/src/templates/legal-assistant.ts +71 -71
  98. package/src/templates/sales-assistant.ts +75 -79
  99. package/src/templates/teacher.ts +75 -79
  100. package/src/testing/index.ts +181 -181
  101. package/src/tools/calculator.ts +73 -73
  102. package/src/tools/datetime.ts +149 -149
  103. package/src/tools/json-transform.ts +187 -187
  104. package/src/tools/mcp.ts +76 -76
  105. package/src/tools/text-analysis.ts +116 -116
  106. package/src/traces/index.ts +132 -0
  107. package/templates/Dockerfile +15 -15
  108. package/templates/code-reviewer/README.md +27 -27
  109. package/templates/code-reviewer/oad.yaml +41 -41
  110. package/templates/customer-service/README.md +22 -22
  111. package/templates/customer-service/oad.yaml +36 -36
  112. package/templates/docker-compose.yml +21 -21
  113. package/templates/ecommerce-assistant/README.md +45 -45
  114. package/templates/ecommerce-assistant/oad.yaml +47 -47
  115. package/templates/knowledge-base/README.md +28 -28
  116. package/templates/knowledge-base/oad.yaml +38 -38
  117. package/templates/sales-assistant/README.md +26 -26
  118. package/templates/sales-assistant/oad.yaml +43 -43
  119. package/templates/tech-support/README.md +43 -43
  120. package/templates/tech-support/oad.yaml +45 -45
  121. package/tests/a2a.test.ts +66 -66
  122. package/tests/agent.test.ts +72 -72
  123. package/tests/analytics.test.ts +50 -50
  124. package/tests/channel.test.ts +39 -39
  125. package/tests/e2e.test.ts +134 -134
  126. package/tests/errors.test.ts +83 -83
  127. package/tests/hitl.test.ts +71 -71
  128. package/tests/i18n.test.ts +41 -41
  129. package/tests/mcp.test.ts +54 -54
  130. package/tests/oad.test.ts +68 -68
  131. package/tests/performance.test.ts +115 -115
  132. package/tests/plugin.test.ts +74 -74
  133. package/tests/room.test.ts +106 -106
  134. package/tests/runtime.test.ts +42 -42
  135. package/tests/sandbox.test.ts +46 -46
  136. package/tests/security.test.ts +60 -60
  137. package/tests/templates.test.ts +77 -77
  138. package/tests/v070.test.ts +76 -76
  139. package/tests/versioning.test.ts +75 -75
  140. package/tests/voice.test.ts +61 -61
  141. package/tests/webhook.test.ts +29 -29
  142. package/tests/workflow.test.ts +143 -143
  143. package/tsconfig.json +19 -19
  144. package/vitest.config.ts +9 -9
  145. package/dist/core/streaming.d.ts +0 -56
  146. package/dist/core/streaming.js +0 -160
  147. package/dist/tools/gateway.d.ts +0 -28
  148. package/dist/tools/gateway.js +0 -177
  149. package/src/dtv/data.ts +0 -29
  150. package/src/dtv/trust.ts +0 -43
  151. package/src/dtv/value.ts +0 -47
  152. package/src/marketplace/index.ts +0 -223
@@ -1,45 +1,45 @@
1
- apiVersion: opc/v1
2
- kind: Agent
3
- metadata:
4
- name: tech-support
5
- version: 1.0.0
6
- description: "技术支持工位 - 处理用户技术问题、故障排查和解决方案推荐"
7
- author: Deepleaper
8
- license: Apache-2.0
9
- marketplace:
10
- certified: false
11
- category: it-support
12
- spec:
13
- provider:
14
- default: deepseek
15
- allowed: [openai, deepseek, qwen, gemini]
16
- model: deepseek-chat
17
- systemPrompt: |
18
- 你是一名专业的技术支持工程师。
19
- 你帮助用户解决技术问题,包括软件故障、系统配置、网络问题、硬件故障等。
20
- 回答时请保持专业、耐心,并提供清晰的步骤指引。
21
- 优先使用知识库中的已知解决方案。
22
- 如果问题无法远程解决,请指导用户联系线下支持或提交工单。
23
- skills:
24
- - name: troubleshoot
25
- description: "诊断和解决技术问题,提供分步骤操作指南"
26
- - name: knowledge-lookup
27
- description: "查询技术文档、FAQ 和历史解决方案库"
28
- - name: ticket-create
29
- description: "为复杂问题创建技术支持工单并跟踪进展"
30
- - name: escalate
31
- description: "将高优先级问题升级到高级工程师或专项团队"
32
- channels:
33
- - type: web
34
- port: 3000
35
- memory:
36
- shortTerm: true
37
- longTerm: true
38
- knowledge:
39
- enabled: true
40
- deepbrain: auto
41
- dtv:
42
- trust:
43
- level: internal
44
- value:
45
- metrics: [resolution_time, first_contact_resolution, customer_satisfaction]
1
+ apiVersion: opc/v1
2
+ kind: Agent
3
+ metadata:
4
+ name: tech-support
5
+ version: 1.0.0
6
+ description: "技术支持工位 - 处理用户技术问题、故障排查和解决方案推荐"
7
+ author: Deepleaper
8
+ license: Apache-2.0
9
+ marketplace:
10
+ certified: false
11
+ category: it-support
12
+ spec:
13
+ provider:
14
+ default: deepseek
15
+ allowed: [openai, deepseek, qwen, gemini]
16
+ model: deepseek-chat
17
+ systemPrompt: |
18
+ 你是一名专业的技术支持工程师。
19
+ 你帮助用户解决技术问题,包括软件故障、系统配置、网络问题、硬件故障等。
20
+ 回答时请保持专业、耐心,并提供清晰的步骤指引。
21
+ 优先使用知识库中的已知解决方案。
22
+ 如果问题无法远程解决,请指导用户联系线下支持或提交工单。
23
+ skills:
24
+ - name: troubleshoot
25
+ description: "诊断和解决技术问题,提供分步骤操作指南"
26
+ - name: knowledge-lookup
27
+ description: "查询技术文档、FAQ 和历史解决方案库"
28
+ - name: ticket-create
29
+ description: "为复杂问题创建技术支持工单并跟踪进展"
30
+ - name: escalate
31
+ description: "将高优先级问题升级到高级工程师或专项团队"
32
+ channels:
33
+ - type: web
34
+ port: 3000
35
+ memory:
36
+ shortTerm: true
37
+ longTerm: true
38
+ knowledge:
39
+ enabled: true
40
+ deepbrain: auto
41
+ dtv:
42
+ trust:
43
+ level: internal
44
+ value:
45
+ metrics: [resolution_time, first_contact_resolution, customer_satisfaction]
package/tests/a2a.test.ts CHANGED
@@ -1,66 +1,66 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { AgentRegistry } from '../src/core/a2a';
3
- import { BaseAgent } from '../src/core/agent';
4
- import { InMemoryStore } from '../src/memory';
5
-
6
- function createTestAgent(name: string): BaseAgent {
7
- const agent = new BaseAgent({ name, model: 'test', systemPrompt: `I am ${name}` }, new InMemoryStore());
8
- return agent;
9
- }
10
-
11
- describe('AgentRegistry (A2A)', () => {
12
- let registry: AgentRegistry;
13
-
14
- beforeEach(() => {
15
- registry = new AgentRegistry();
16
- });
17
-
18
- it('should register and discover agents', () => {
19
- const agent = createTestAgent('agent-1');
20
- registry.register(agent, [{ name: 'summarize', description: 'Summarize text' }]);
21
-
22
- const all = registry.discover();
23
- expect(all).toHaveLength(1);
24
- expect(all[0].agentName).toBe('agent-1');
25
- });
26
-
27
- it('should discover by capability', () => {
28
- const a1 = createTestAgent('a1');
29
- const a2 = createTestAgent('a2');
30
- registry.register(a1, [{ name: 'summarize', description: 'Summarize' }]);
31
- registry.register(a2, [{ name: 'translate', description: 'Translate' }]);
32
-
33
- const found = registry.discover('translate');
34
- expect(found).toHaveLength(1);
35
- expect(found[0].agentName).toBe('a2');
36
- });
37
-
38
- it('should unregister agents', () => {
39
- const agent = createTestAgent('temp');
40
- registry.register(agent, []);
41
- registry.unregister('temp');
42
- expect(registry.discover()).toHaveLength(0);
43
- });
44
-
45
- it('should handle request to unknown agent', async () => {
46
- const response = await registry.call('a1', 'unknown', 'test', 'hello');
47
- expect(response.status).toBe('error');
48
- });
49
-
50
- it('should send A2A request and get response', async () => {
51
- const agent = createTestAgent('responder');
52
- await agent.init();
53
- registry.register(agent, [{ name: 'chat', description: 'Chat' }]);
54
-
55
- const response = await registry.call('caller', 'responder', 'chat', 'hello');
56
- expect(response.status).toBe('success');
57
- expect(response.payload).toBeDefined();
58
- });
59
-
60
- it('should get agent by name', () => {
61
- const agent = createTestAgent('finder');
62
- registry.register(agent, []);
63
- expect(registry.getAgent('finder')).toBeDefined();
64
- expect(registry.getAgent('none')).toBeUndefined();
65
- });
66
- });
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { AgentRegistry } from '../src/core/a2a';
3
+ import { BaseAgent } from '../src/core/agent';
4
+ import { InMemoryStore } from '../src/memory';
5
+
6
+ function createTestAgent(name: string): BaseAgent {
7
+ const agent = new BaseAgent({ name, model: 'test', systemPrompt: `I am ${name}` }, new InMemoryStore());
8
+ return agent;
9
+ }
10
+
11
+ describe('AgentRegistry (A2A)', () => {
12
+ let registry: AgentRegistry;
13
+
14
+ beforeEach(() => {
15
+ registry = new AgentRegistry();
16
+ });
17
+
18
+ it('should register and discover agents', () => {
19
+ const agent = createTestAgent('agent-1');
20
+ registry.register(agent, [{ name: 'summarize', description: 'Summarize text' }]);
21
+
22
+ const all = registry.discover();
23
+ expect(all).toHaveLength(1);
24
+ expect(all[0].agentName).toBe('agent-1');
25
+ });
26
+
27
+ it('should discover by capability', () => {
28
+ const a1 = createTestAgent('a1');
29
+ const a2 = createTestAgent('a2');
30
+ registry.register(a1, [{ name: 'summarize', description: 'Summarize' }]);
31
+ registry.register(a2, [{ name: 'translate', description: 'Translate' }]);
32
+
33
+ const found = registry.discover('translate');
34
+ expect(found).toHaveLength(1);
35
+ expect(found[0].agentName).toBe('a2');
36
+ });
37
+
38
+ it('should unregister agents', () => {
39
+ const agent = createTestAgent('temp');
40
+ registry.register(agent, []);
41
+ registry.unregister('temp');
42
+ expect(registry.discover()).toHaveLength(0);
43
+ });
44
+
45
+ it('should handle request to unknown agent', async () => {
46
+ const response = await registry.call('a1', 'unknown', 'test', 'hello');
47
+ expect(response.status).toBe('error');
48
+ });
49
+
50
+ it('should send A2A request and get response', async () => {
51
+ const agent = createTestAgent('responder');
52
+ await agent.init();
53
+ registry.register(agent, [{ name: 'chat', description: 'Chat' }]);
54
+
55
+ const response = await registry.call('caller', 'responder', 'chat', 'hello');
56
+ expect(response.status).toBe('success');
57
+ expect(response.payload).toBeDefined();
58
+ });
59
+
60
+ it('should get agent by name', () => {
61
+ const agent = createTestAgent('finder');
62
+ registry.register(agent, []);
63
+ expect(registry.getAgent('finder')).toBeDefined();
64
+ expect(registry.getAgent('none')).toBeUndefined();
65
+ });
66
+ });
@@ -1,72 +1,72 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { BaseAgent } from '../src/core/agent';
3
-
4
- describe('BaseAgent', () => {
5
- it('should start in init state', () => {
6
- const agent = new BaseAgent({ name: 'test' });
7
- expect(agent.state).toBe('init');
8
- expect(agent.name).toBe('test');
9
- });
10
-
11
- it('should transition through lifecycle', async () => {
12
- const agent = new BaseAgent({ name: 'test' });
13
- const states: string[] = [];
14
- agent.on('state:change', (_from, to) => states.push(to));
15
-
16
- await agent.init();
17
- expect(agent.state).toBe('ready');
18
-
19
- await agent.start();
20
- expect(agent.state).toBe('running');
21
-
22
- await agent.stop();
23
- expect(agent.state).toBe('stopped');
24
-
25
- expect(states).toEqual(['ready', 'running', 'stopped']);
26
- });
27
-
28
- it('should not start if not ready', async () => {
29
- const agent = new BaseAgent({ name: 'test' });
30
- await expect(agent.start()).rejects.toThrow('Cannot start agent in state: init');
31
- });
32
-
33
- it('should handle messages with stub provider', async () => {
34
- const agent = new BaseAgent({ name: 'test', systemPrompt: 'Be helpful' });
35
- await agent.init();
36
-
37
- const response = await agent.handleMessage({
38
- id: 'msg_1',
39
- role: 'user',
40
- content: 'Hello',
41
- timestamp: Date.now(),
42
- });
43
-
44
- expect(response.role).toBe('assistant');
45
- expect(response.content).toContain('Hello');
46
- });
47
-
48
- it('should use skills before LLM fallback', async () => {
49
- const agent = new BaseAgent({ name: 'test' });
50
- await agent.init();
51
-
52
- agent.registerSkill({
53
- name: 'greeter',
54
- description: 'Greets users',
55
- execute: async (_ctx, msg) => {
56
- if (msg.content.toLowerCase().includes('hello')) {
57
- return { handled: true, response: 'Hi there!', confidence: 1.0 };
58
- }
59
- return { handled: false, confidence: 0 };
60
- },
61
- });
62
-
63
- const response = await agent.handleMessage({
64
- id: 'msg_1',
65
- role: 'user',
66
- content: 'Hello!',
67
- timestamp: Date.now(),
68
- });
69
-
70
- expect(response.content).toBe('Hi there!');
71
- });
72
- });
1
+ import { describe, it, expect } from 'vitest';
2
+ import { BaseAgent } from '../src/core/agent';
3
+
4
+ describe('BaseAgent', () => {
5
+ it('should start in init state', () => {
6
+ const agent = new BaseAgent({ name: 'test' });
7
+ expect(agent.state).toBe('init');
8
+ expect(agent.name).toBe('test');
9
+ });
10
+
11
+ it('should transition through lifecycle', async () => {
12
+ const agent = new BaseAgent({ name: 'test' });
13
+ const states: string[] = [];
14
+ agent.on('state:change', (_from, to) => states.push(to));
15
+
16
+ await agent.init();
17
+ expect(agent.state).toBe('ready');
18
+
19
+ await agent.start();
20
+ expect(agent.state).toBe('running');
21
+
22
+ await agent.stop();
23
+ expect(agent.state).toBe('stopped');
24
+
25
+ expect(states).toEqual(['ready', 'running', 'stopped']);
26
+ });
27
+
28
+ it('should not start if not ready', async () => {
29
+ const agent = new BaseAgent({ name: 'test' });
30
+ await expect(agent.start()).rejects.toThrow('Cannot start agent in state: init');
31
+ });
32
+
33
+ it('should handle messages with stub provider', async () => {
34
+ const agent = new BaseAgent({ name: 'test', systemPrompt: 'Be helpful' });
35
+ await agent.init();
36
+
37
+ const response = await agent.handleMessage({
38
+ id: 'msg_1',
39
+ role: 'user',
40
+ content: 'Hello',
41
+ timestamp: Date.now(),
42
+ });
43
+
44
+ expect(response.role).toBe('assistant');
45
+ expect(response.content).toContain('Hello');
46
+ });
47
+
48
+ it('should use skills before LLM fallback', async () => {
49
+ const agent = new BaseAgent({ name: 'test' });
50
+ await agent.init();
51
+
52
+ agent.registerSkill({
53
+ name: 'greeter',
54
+ description: 'Greets users',
55
+ execute: async (_ctx, msg) => {
56
+ if (msg.content.toLowerCase().includes('hello')) {
57
+ return { handled: true, response: 'Hi there!', confidence: 1.0 };
58
+ }
59
+ return { handled: false, confidence: 0 };
60
+ },
61
+ });
62
+
63
+ const response = await agent.handleMessage({
64
+ id: 'msg_1',
65
+ role: 'user',
66
+ content: 'Hello!',
67
+ timestamp: Date.now(),
68
+ });
69
+
70
+ expect(response.content).toBe('Hi there!');
71
+ });
72
+ });
@@ -1,50 +1,50 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Analytics } from '../src/analytics';
3
-
4
- describe('Analytics', () => {
5
- it('should track messages', () => {
6
- const analytics = new Analytics();
7
- analytics.recordMessage(100);
8
- analytics.recordMessage(200);
9
- const snap = analytics.getSnapshot();
10
- expect(snap.messagesProcessed).toBe(2);
11
- expect(snap.avgResponseTimeMs).toBe(150);
12
- });
13
-
14
- it('should track skill usage', () => {
15
- const analytics = new Analytics();
16
- analytics.recordSkillUsage('faq');
17
- analytics.recordSkillUsage('faq');
18
- analytics.recordSkillUsage('handoff');
19
- const snap = analytics.getSnapshot();
20
- expect(snap.skillUsage['faq']).toBe(2);
21
- expect(snap.skillUsage['handoff']).toBe(1);
22
- });
23
-
24
- it('should track errors', () => {
25
- const analytics = new Analytics();
26
- analytics.recordError();
27
- analytics.recordError();
28
- expect(analytics.getSnapshot().errorCount).toBe(2);
29
- });
30
-
31
- it('should track token usage', () => {
32
- const analytics = new Analytics();
33
- analytics.recordTokens(100, 50);
34
- analytics.recordTokens(200, 100);
35
- const snap = analytics.getSnapshot();
36
- expect(snap.tokenUsage.input).toBe(300);
37
- expect(snap.tokenUsage.output).toBe(150);
38
- expect(snap.tokenUsage.total).toBe(450);
39
- });
40
-
41
- it('should reset analytics', () => {
42
- const analytics = new Analytics();
43
- analytics.recordMessage(100);
44
- analytics.recordError();
45
- analytics.reset();
46
- const snap = analytics.getSnapshot();
47
- expect(snap.messagesProcessed).toBe(0);
48
- expect(snap.errorCount).toBe(0);
49
- });
50
- });
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Analytics } from '../src/analytics';
3
+
4
+ describe('Analytics', () => {
5
+ it('should track messages', () => {
6
+ const analytics = new Analytics();
7
+ analytics.recordMessage(100);
8
+ analytics.recordMessage(200);
9
+ const snap = analytics.getSnapshot();
10
+ expect(snap.messagesProcessed).toBe(2);
11
+ expect(snap.avgResponseTimeMs).toBe(150);
12
+ });
13
+
14
+ it('should track skill usage', () => {
15
+ const analytics = new Analytics();
16
+ analytics.recordSkillUsage('faq');
17
+ analytics.recordSkillUsage('faq');
18
+ analytics.recordSkillUsage('handoff');
19
+ const snap = analytics.getSnapshot();
20
+ expect(snap.skillUsage['faq']).toBe(2);
21
+ expect(snap.skillUsage['handoff']).toBe(1);
22
+ });
23
+
24
+ it('should track errors', () => {
25
+ const analytics = new Analytics();
26
+ analytics.recordError();
27
+ analytics.recordError();
28
+ expect(analytics.getSnapshot().errorCount).toBe(2);
29
+ });
30
+
31
+ it('should track token usage', () => {
32
+ const analytics = new Analytics();
33
+ analytics.recordTokens(100, 50);
34
+ analytics.recordTokens(200, 100);
35
+ const snap = analytics.getSnapshot();
36
+ expect(snap.tokenUsage.input).toBe(300);
37
+ expect(snap.tokenUsage.output).toBe(150);
38
+ expect(snap.tokenUsage.total).toBe(450);
39
+ });
40
+
41
+ it('should reset analytics', () => {
42
+ const analytics = new Analytics();
43
+ analytics.recordMessage(100);
44
+ analytics.recordError();
45
+ analytics.reset();
46
+ const snap = analytics.getSnapshot();
47
+ expect(snap.messagesProcessed).toBe(0);
48
+ expect(snap.errorCount).toBe(0);
49
+ });
50
+ });
@@ -1,39 +1,39 @@
1
- import { describe, it, expect, afterEach } from 'vitest';
2
- import { WebChannel } from '../src/channels/web';
3
-
4
- describe('WebChannel', () => {
5
- let channel: WebChannel | null = null;
6
-
7
- afterEach(async () => {
8
- if (channel) {
9
- await channel.stop();
10
- channel = null;
11
- }
12
- });
13
-
14
- it('should create with default port', () => {
15
- channel = new WebChannel();
16
- expect(channel.type).toBe('web');
17
- });
18
-
19
- it('should start and stop', async () => {
20
- channel = new WebChannel(0); // random port
21
- channel.onMessage(async (msg) => ({
22
- id: 'resp_1',
23
- role: 'assistant',
24
- content: `Echo: ${msg.content}`,
25
- timestamp: Date.now(),
26
- }));
27
- await channel.start();
28
- await channel.stop();
29
- channel = null;
30
- });
31
-
32
- it('should handle health check', async () => {
33
- channel = new WebChannel(0);
34
- await channel.start();
35
- // Channel is running — health endpoint is available
36
- await channel.stop();
37
- channel = null;
38
- });
39
- });
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { WebChannel } from '../src/channels/web';
3
+
4
+ describe('WebChannel', () => {
5
+ let channel: WebChannel | null = null;
6
+
7
+ afterEach(async () => {
8
+ if (channel) {
9
+ await channel.stop();
10
+ channel = null;
11
+ }
12
+ });
13
+
14
+ it('should create with default port', () => {
15
+ channel = new WebChannel();
16
+ expect(channel.type).toBe('web');
17
+ });
18
+
19
+ it('should start and stop', async () => {
20
+ channel = new WebChannel(0); // random port
21
+ channel.onMessage(async (msg) => ({
22
+ id: 'resp_1',
23
+ role: 'assistant',
24
+ content: `Echo: ${msg.content}`,
25
+ timestamp: Date.now(),
26
+ }));
27
+ await channel.start();
28
+ await channel.stop();
29
+ channel = null;
30
+ });
31
+
32
+ it('should handle health check', async () => {
33
+ channel = new WebChannel(0);
34
+ await channel.start();
35
+ // Channel is running — health endpoint is available
36
+ await channel.stop();
37
+ channel = null;
38
+ });
39
+ });