opc-agent 4.0.44 → 4.1.1
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/dist/channels/wechat.js +6 -6
- package/dist/cli.js +2 -2
- package/dist/core/runtime.js +18 -0
- package/dist/deploy/index.js +56 -56
- package/dist/providers/index.js +39 -13
- package/dist/studio/server.js +211 -20
- package/dist/studio-ui/index.html +279 -24
- 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/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 +2 -2
- 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 +18 -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 +156 -156
- 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/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 -608
- 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 +209 -22
- package/src/studio/templates-data.ts +178 -178
- package/src/studio-ui/index.html +279 -24
- 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/brain-seed.test.ts
CHANGED
|
@@ -1,239 +1,239 @@
|
|
|
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 { BrainSeedLoader, KnowledgeEvolver } from '../src/memory/seed-loader';
|
|
6
|
-
import type { BrainSeedConfig } from '../src/memory/seed-loader';
|
|
7
|
-
|
|
8
|
-
function makeTmpDir(): string {
|
|
9
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opc-seed-test-'));
|
|
10
|
-
return dir;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe('BrainSeedLoader', () => {
|
|
14
|
-
let tmpDir: string;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
tmpDir = makeTmpDir();
|
|
18
|
-
fs.mkdirSync(path.join(tmpDir, 'brain-seeds'), { recursive: true });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('parseSeedFile splits ## sections into pages', () => {
|
|
26
|
-
const seedFile = path.join(tmpDir, 'brain-seeds', 'industry.md');
|
|
27
|
-
fs.writeFileSync(seedFile, `# Industry Knowledge
|
|
28
|
-
|
|
29
|
-
## E-commerce Basics
|
|
30
|
-
|
|
31
|
-
Online retail fundamentals.
|
|
32
|
-
|
|
33
|
-
## Payment Systems
|
|
34
|
-
|
|
35
|
-
How payments work in e-commerce.
|
|
36
|
-
|
|
37
|
-
## Logistics
|
|
38
|
-
|
|
39
|
-
Shipping and fulfillment.
|
|
40
|
-
`);
|
|
41
|
-
const loader = new BrainSeedLoader(tmpDir, { seeds: ['brain-seeds/industry.md'], autoSeed: true });
|
|
42
|
-
const pages = loader.parseSeedFile(seedFile, 'industry');
|
|
43
|
-
|
|
44
|
-
expect(pages).toHaveLength(3);
|
|
45
|
-
expect(pages[0].slug).toBe('seed/industry/e-commerce-basics');
|
|
46
|
-
expect(pages[0].tier).toBe('industry');
|
|
47
|
-
expect(pages[0].content).toContain('E-commerce Basics');
|
|
48
|
-
expect(pages[0].content).toContain('Online retail fundamentals.');
|
|
49
|
-
expect(pages[1].slug).toBe('seed/industry/payment-systems');
|
|
50
|
-
expect(pages[2].slug).toBe('seed/industry/logistics');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('isSeeded returns false when no marker file', async () => {
|
|
54
|
-
const loader = new BrainSeedLoader(tmpDir, { seeds: [], autoSeed: true });
|
|
55
|
-
expect(await loader.isSeeded()).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('isSeeded returns true after markSeeded', async () => {
|
|
59
|
-
const loader = new BrainSeedLoader(tmpDir, { seeds: [], autoSeed: true });
|
|
60
|
-
await loader.markSeeded();
|
|
61
|
-
expect(await loader.isSeeded()).toBe(true);
|
|
62
|
-
// Check marker file content
|
|
63
|
-
const marker = JSON.parse(fs.readFileSync(path.join(tmpDir, '.brain-seeded'), 'utf-8'));
|
|
64
|
-
expect(marker.seededAt).toBeDefined();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('seedBrain imports pages with learn()', async () => {
|
|
68
|
-
const seedFile = path.join(tmpDir, 'brain-seeds', 'job.md');
|
|
69
|
-
fs.writeFileSync(seedFile, `# Job Knowledge
|
|
70
|
-
|
|
71
|
-
## Customer Handling
|
|
72
|
-
|
|
73
|
-
How to handle customers.
|
|
74
|
-
|
|
75
|
-
## Escalation
|
|
76
|
-
|
|
77
|
-
When to escalate.
|
|
78
|
-
`);
|
|
79
|
-
|
|
80
|
-
const learned: { content: string; meta: any }[] = [];
|
|
81
|
-
const mockBrain = {
|
|
82
|
-
learn: async (content: string, meta: any) => { learned.push({ content, meta }); },
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const loader = new BrainSeedLoader(tmpDir, { seeds: ['brain-seeds/job.md'], autoSeed: true });
|
|
86
|
-
const result = await loader.seedBrain(mockBrain);
|
|
87
|
-
|
|
88
|
-
expect(result.imported).toBe(2);
|
|
89
|
-
expect(result.pages).toContain('seed/job/customer-handling');
|
|
90
|
-
expect(result.pages).toContain('seed/job/escalation');
|
|
91
|
-
expect(learned).toHaveLength(2);
|
|
92
|
-
expect(learned[0].meta.tags).toContain('brain-seed');
|
|
93
|
-
expect(learned[0].meta.tags).toContain('job');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('seedBrain imports pages with store()', async () => {
|
|
97
|
-
const seedFile = path.join(tmpDir, 'brain-seeds', 'industry.md');
|
|
98
|
-
fs.writeFileSync(seedFile, `## Topic One\n\nContent one.\n`);
|
|
99
|
-
|
|
100
|
-
const stored: any[] = [];
|
|
101
|
-
const mockBrain = {
|
|
102
|
-
store: async (collection: string, slug: string, content: string, meta: any) => {
|
|
103
|
-
stored.push({ collection, slug, content, meta });
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const loader = new BrainSeedLoader(tmpDir, { seeds: ['brain-seeds/industry.md'], autoSeed: true });
|
|
108
|
-
const result = await loader.seedBrain(mockBrain);
|
|
109
|
-
|
|
110
|
-
expect(result.imported).toBe(1);
|
|
111
|
-
expect(stored[0].collection).toBe('brain-seeds');
|
|
112
|
-
expect(stored[0].meta.tier).toBe('industry');
|
|
113
|
-
expect(stored[0].meta.source).toBe('brain-seed');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('skips import if already seeded', async () => {
|
|
117
|
-
const seedFile = path.join(tmpDir, 'brain-seeds', 'industry.md');
|
|
118
|
-
fs.writeFileSync(seedFile, `## Topic\n\nContent.\n`);
|
|
119
|
-
|
|
120
|
-
const loader = new BrainSeedLoader(tmpDir, { seeds: ['brain-seeds/industry.md'], autoSeed: true });
|
|
121
|
-
await loader.markSeeded();
|
|
122
|
-
|
|
123
|
-
expect(await loader.isSeeded()).toBe(true);
|
|
124
|
-
// seedBrain would still work if called, but the guard is in agent.ts
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('handles missing seed files gracefully', async () => {
|
|
128
|
-
const loader = new BrainSeedLoader(tmpDir, {
|
|
129
|
-
seeds: ['brain-seeds/nonexistent.md'],
|
|
130
|
-
autoSeed: true,
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const mockBrain = { learn: async () => {} };
|
|
134
|
-
const result = await loader.seedBrain(mockBrain);
|
|
135
|
-
expect(result.imported).toBe(0);
|
|
136
|
-
expect(result.pages).toHaveLength(0);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('infers tier from filename', () => {
|
|
140
|
-
// Write all three seed files
|
|
141
|
-
fs.writeFileSync(path.join(tmpDir, 'brain-seeds', 'industry.md'), '## Ind Topic\n\nContent.\n');
|
|
142
|
-
fs.writeFileSync(path.join(tmpDir, 'brain-seeds', 'job.md'), '## Job Topic\n\nContent.\n');
|
|
143
|
-
fs.writeFileSync(path.join(tmpDir, 'brain-seeds', 'workstation.md'), '## WS Topic\n\nContent.\n');
|
|
144
|
-
|
|
145
|
-
const loader = new BrainSeedLoader(tmpDir, {
|
|
146
|
-
seeds: ['brain-seeds/industry.md', 'brain-seeds/job.md', 'brain-seeds/workstation.md'],
|
|
147
|
-
autoSeed: true,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const indPages = loader.parseSeedFile(path.join(tmpDir, 'brain-seeds', 'industry.md'), 'industry');
|
|
151
|
-
const jobPages = loader.parseSeedFile(path.join(tmpDir, 'brain-seeds', 'job.md'), 'job');
|
|
152
|
-
const wsPages = loader.parseSeedFile(path.join(tmpDir, 'brain-seeds', 'workstation.md'), 'workstation');
|
|
153
|
-
|
|
154
|
-
expect(indPages[0].tier).toBe('industry');
|
|
155
|
-
expect(indPages[0].slug).toContain('seed/industry/');
|
|
156
|
-
expect(jobPages[0].tier).toBe('job');
|
|
157
|
-
expect(wsPages[0].tier).toBe('workstation');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('uses custom seedMarkerFile', async () => {
|
|
161
|
-
const loader = new BrainSeedLoader(tmpDir, {
|
|
162
|
-
seeds: [],
|
|
163
|
-
autoSeed: true,
|
|
164
|
-
seedMarkerFile: '.custom-marker',
|
|
165
|
-
});
|
|
166
|
-
await loader.markSeeded();
|
|
167
|
-
expect(fs.existsSync(path.join(tmpDir, '.custom-marker'))).toBe(true);
|
|
168
|
-
expect(await loader.isSeeded()).toBe(true);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe('KnowledgeEvolver', () => {
|
|
173
|
-
it('checkPromotion returns empty when brain is null', async () => {
|
|
174
|
-
const evolver = new KnowledgeEvolver();
|
|
175
|
-
const result = await evolver.checkPromotion(null);
|
|
176
|
-
expect(result.candidates).toHaveLength(0);
|
|
177
|
-
expect(result.promoted).toBe(0);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('promoteToJob calls brain.store with job tier', async () => {
|
|
181
|
-
const stored: any[] = [];
|
|
182
|
-
const mockBrain = {
|
|
183
|
-
store: async (col: string, slug: string, content: string, meta: any) => {
|
|
184
|
-
stored.push({ col, slug, content, meta });
|
|
185
|
-
},
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const evolver = new KnowledgeEvolver();
|
|
189
|
-
await evolver.promoteToJob(mockBrain, 'Important knowledge', 'seed/job/promoted-topic');
|
|
190
|
-
|
|
191
|
-
expect(stored).toHaveLength(1);
|
|
192
|
-
expect(stored[0].meta.tier).toBe('job');
|
|
193
|
-
expect(stored[0].meta.source).toBe('promotion');
|
|
194
|
-
expect(stored[0].slug).toBe('seed/job/promoted-topic');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('promoteToIndustry calls brain.store with industry tier', async () => {
|
|
198
|
-
const stored: any[] = [];
|
|
199
|
-
const mockBrain = {
|
|
200
|
-
store: async (col: string, slug: string, content: string, meta: any) => {
|
|
201
|
-
stored.push({ col, slug, content, meta });
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const evolver = new KnowledgeEvolver();
|
|
206
|
-
await evolver.promoteToIndustry(mockBrain, 'Cross-role knowledge', 'seed/industry/common-pattern');
|
|
207
|
-
|
|
208
|
-
expect(stored).toHaveLength(1);
|
|
209
|
-
expect(stored[0].meta.tier).toBe('industry');
|
|
210
|
-
expect(stored[0].meta.source).toBe('promotion');
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe('OAD spec.brain.seeds config parsing', () => {
|
|
215
|
-
it('parses brain seed config from YAML', () => {
|
|
216
|
-
const yaml = require('js-yaml');
|
|
217
|
-
const config = yaml.load(`
|
|
218
|
-
apiVersion: opc/v1
|
|
219
|
-
kind: Agent
|
|
220
|
-
metadata:
|
|
221
|
-
name: test
|
|
222
|
-
spec:
|
|
223
|
-
brain:
|
|
224
|
-
seeds:
|
|
225
|
-
- brain-seeds/industry.md
|
|
226
|
-
- brain-seeds/job.md
|
|
227
|
-
- brain-seeds/workstation.md
|
|
228
|
-
autoSeed: true
|
|
229
|
-
evolve:
|
|
230
|
-
enabled: true
|
|
231
|
-
direction: bottom-up
|
|
232
|
-
`) as any;
|
|
233
|
-
|
|
234
|
-
expect(config.spec.brain.seeds).toHaveLength(3);
|
|
235
|
-
expect(config.spec.brain.autoSeed).toBe(true);
|
|
236
|
-
expect(config.spec.brain.evolve.enabled).toBe(true);
|
|
237
|
-
expect(config.spec.brain.evolve.direction).toBe('bottom-up');
|
|
238
|
-
});
|
|
239
|
-
});
|
|
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 { BrainSeedLoader, KnowledgeEvolver } from '../src/memory/seed-loader';
|
|
6
|
+
import type { BrainSeedConfig } from '../src/memory/seed-loader';
|
|
7
|
+
|
|
8
|
+
function makeTmpDir(): string {
|
|
9
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opc-seed-test-'));
|
|
10
|
+
return dir;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('BrainSeedLoader', () => {
|
|
14
|
+
let tmpDir: string;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
tmpDir = makeTmpDir();
|
|
18
|
+
fs.mkdirSync(path.join(tmpDir, 'brain-seeds'), { recursive: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('parseSeedFile splits ## sections into pages', () => {
|
|
26
|
+
const seedFile = path.join(tmpDir, 'brain-seeds', 'industry.md');
|
|
27
|
+
fs.writeFileSync(seedFile, `# Industry Knowledge
|
|
28
|
+
|
|
29
|
+
## E-commerce Basics
|
|
30
|
+
|
|
31
|
+
Online retail fundamentals.
|
|
32
|
+
|
|
33
|
+
## Payment Systems
|
|
34
|
+
|
|
35
|
+
How payments work in e-commerce.
|
|
36
|
+
|
|
37
|
+
## Logistics
|
|
38
|
+
|
|
39
|
+
Shipping and fulfillment.
|
|
40
|
+
`);
|
|
41
|
+
const loader = new BrainSeedLoader(tmpDir, { seeds: ['brain-seeds/industry.md'], autoSeed: true });
|
|
42
|
+
const pages = loader.parseSeedFile(seedFile, 'industry');
|
|
43
|
+
|
|
44
|
+
expect(pages).toHaveLength(3);
|
|
45
|
+
expect(pages[0].slug).toBe('seed/industry/e-commerce-basics');
|
|
46
|
+
expect(pages[0].tier).toBe('industry');
|
|
47
|
+
expect(pages[0].content).toContain('E-commerce Basics');
|
|
48
|
+
expect(pages[0].content).toContain('Online retail fundamentals.');
|
|
49
|
+
expect(pages[1].slug).toBe('seed/industry/payment-systems');
|
|
50
|
+
expect(pages[2].slug).toBe('seed/industry/logistics');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('isSeeded returns false when no marker file', async () => {
|
|
54
|
+
const loader = new BrainSeedLoader(tmpDir, { seeds: [], autoSeed: true });
|
|
55
|
+
expect(await loader.isSeeded()).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('isSeeded returns true after markSeeded', async () => {
|
|
59
|
+
const loader = new BrainSeedLoader(tmpDir, { seeds: [], autoSeed: true });
|
|
60
|
+
await loader.markSeeded();
|
|
61
|
+
expect(await loader.isSeeded()).toBe(true);
|
|
62
|
+
// Check marker file content
|
|
63
|
+
const marker = JSON.parse(fs.readFileSync(path.join(tmpDir, '.brain-seeded'), 'utf-8'));
|
|
64
|
+
expect(marker.seededAt).toBeDefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('seedBrain imports pages with learn()', async () => {
|
|
68
|
+
const seedFile = path.join(tmpDir, 'brain-seeds', 'job.md');
|
|
69
|
+
fs.writeFileSync(seedFile, `# Job Knowledge
|
|
70
|
+
|
|
71
|
+
## Customer Handling
|
|
72
|
+
|
|
73
|
+
How to handle customers.
|
|
74
|
+
|
|
75
|
+
## Escalation
|
|
76
|
+
|
|
77
|
+
When to escalate.
|
|
78
|
+
`);
|
|
79
|
+
|
|
80
|
+
const learned: { content: string; meta: any }[] = [];
|
|
81
|
+
const mockBrain = {
|
|
82
|
+
learn: async (content: string, meta: any) => { learned.push({ content, meta }); },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const loader = new BrainSeedLoader(tmpDir, { seeds: ['brain-seeds/job.md'], autoSeed: true });
|
|
86
|
+
const result = await loader.seedBrain(mockBrain);
|
|
87
|
+
|
|
88
|
+
expect(result.imported).toBe(2);
|
|
89
|
+
expect(result.pages).toContain('seed/job/customer-handling');
|
|
90
|
+
expect(result.pages).toContain('seed/job/escalation');
|
|
91
|
+
expect(learned).toHaveLength(2);
|
|
92
|
+
expect(learned[0].meta.tags).toContain('brain-seed');
|
|
93
|
+
expect(learned[0].meta.tags).toContain('job');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('seedBrain imports pages with store()', async () => {
|
|
97
|
+
const seedFile = path.join(tmpDir, 'brain-seeds', 'industry.md');
|
|
98
|
+
fs.writeFileSync(seedFile, `## Topic One\n\nContent one.\n`);
|
|
99
|
+
|
|
100
|
+
const stored: any[] = [];
|
|
101
|
+
const mockBrain = {
|
|
102
|
+
store: async (collection: string, slug: string, content: string, meta: any) => {
|
|
103
|
+
stored.push({ collection, slug, content, meta });
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const loader = new BrainSeedLoader(tmpDir, { seeds: ['brain-seeds/industry.md'], autoSeed: true });
|
|
108
|
+
const result = await loader.seedBrain(mockBrain);
|
|
109
|
+
|
|
110
|
+
expect(result.imported).toBe(1);
|
|
111
|
+
expect(stored[0].collection).toBe('brain-seeds');
|
|
112
|
+
expect(stored[0].meta.tier).toBe('industry');
|
|
113
|
+
expect(stored[0].meta.source).toBe('brain-seed');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('skips import if already seeded', async () => {
|
|
117
|
+
const seedFile = path.join(tmpDir, 'brain-seeds', 'industry.md');
|
|
118
|
+
fs.writeFileSync(seedFile, `## Topic\n\nContent.\n`);
|
|
119
|
+
|
|
120
|
+
const loader = new BrainSeedLoader(tmpDir, { seeds: ['brain-seeds/industry.md'], autoSeed: true });
|
|
121
|
+
await loader.markSeeded();
|
|
122
|
+
|
|
123
|
+
expect(await loader.isSeeded()).toBe(true);
|
|
124
|
+
// seedBrain would still work if called, but the guard is in agent.ts
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('handles missing seed files gracefully', async () => {
|
|
128
|
+
const loader = new BrainSeedLoader(tmpDir, {
|
|
129
|
+
seeds: ['brain-seeds/nonexistent.md'],
|
|
130
|
+
autoSeed: true,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const mockBrain = { learn: async () => {} };
|
|
134
|
+
const result = await loader.seedBrain(mockBrain);
|
|
135
|
+
expect(result.imported).toBe(0);
|
|
136
|
+
expect(result.pages).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('infers tier from filename', () => {
|
|
140
|
+
// Write all three seed files
|
|
141
|
+
fs.writeFileSync(path.join(tmpDir, 'brain-seeds', 'industry.md'), '## Ind Topic\n\nContent.\n');
|
|
142
|
+
fs.writeFileSync(path.join(tmpDir, 'brain-seeds', 'job.md'), '## Job Topic\n\nContent.\n');
|
|
143
|
+
fs.writeFileSync(path.join(tmpDir, 'brain-seeds', 'workstation.md'), '## WS Topic\n\nContent.\n');
|
|
144
|
+
|
|
145
|
+
const loader = new BrainSeedLoader(tmpDir, {
|
|
146
|
+
seeds: ['brain-seeds/industry.md', 'brain-seeds/job.md', 'brain-seeds/workstation.md'],
|
|
147
|
+
autoSeed: true,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const indPages = loader.parseSeedFile(path.join(tmpDir, 'brain-seeds', 'industry.md'), 'industry');
|
|
151
|
+
const jobPages = loader.parseSeedFile(path.join(tmpDir, 'brain-seeds', 'job.md'), 'job');
|
|
152
|
+
const wsPages = loader.parseSeedFile(path.join(tmpDir, 'brain-seeds', 'workstation.md'), 'workstation');
|
|
153
|
+
|
|
154
|
+
expect(indPages[0].tier).toBe('industry');
|
|
155
|
+
expect(indPages[0].slug).toContain('seed/industry/');
|
|
156
|
+
expect(jobPages[0].tier).toBe('job');
|
|
157
|
+
expect(wsPages[0].tier).toBe('workstation');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('uses custom seedMarkerFile', async () => {
|
|
161
|
+
const loader = new BrainSeedLoader(tmpDir, {
|
|
162
|
+
seeds: [],
|
|
163
|
+
autoSeed: true,
|
|
164
|
+
seedMarkerFile: '.custom-marker',
|
|
165
|
+
});
|
|
166
|
+
await loader.markSeeded();
|
|
167
|
+
expect(fs.existsSync(path.join(tmpDir, '.custom-marker'))).toBe(true);
|
|
168
|
+
expect(await loader.isSeeded()).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('KnowledgeEvolver', () => {
|
|
173
|
+
it('checkPromotion returns empty when brain is null', async () => {
|
|
174
|
+
const evolver = new KnowledgeEvolver();
|
|
175
|
+
const result = await evolver.checkPromotion(null);
|
|
176
|
+
expect(result.candidates).toHaveLength(0);
|
|
177
|
+
expect(result.promoted).toBe(0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('promoteToJob calls brain.store with job tier', async () => {
|
|
181
|
+
const stored: any[] = [];
|
|
182
|
+
const mockBrain = {
|
|
183
|
+
store: async (col: string, slug: string, content: string, meta: any) => {
|
|
184
|
+
stored.push({ col, slug, content, meta });
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const evolver = new KnowledgeEvolver();
|
|
189
|
+
await evolver.promoteToJob(mockBrain, 'Important knowledge', 'seed/job/promoted-topic');
|
|
190
|
+
|
|
191
|
+
expect(stored).toHaveLength(1);
|
|
192
|
+
expect(stored[0].meta.tier).toBe('job');
|
|
193
|
+
expect(stored[0].meta.source).toBe('promotion');
|
|
194
|
+
expect(stored[0].slug).toBe('seed/job/promoted-topic');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('promoteToIndustry calls brain.store with industry tier', async () => {
|
|
198
|
+
const stored: any[] = [];
|
|
199
|
+
const mockBrain = {
|
|
200
|
+
store: async (col: string, slug: string, content: string, meta: any) => {
|
|
201
|
+
stored.push({ col, slug, content, meta });
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const evolver = new KnowledgeEvolver();
|
|
206
|
+
await evolver.promoteToIndustry(mockBrain, 'Cross-role knowledge', 'seed/industry/common-pattern');
|
|
207
|
+
|
|
208
|
+
expect(stored).toHaveLength(1);
|
|
209
|
+
expect(stored[0].meta.tier).toBe('industry');
|
|
210
|
+
expect(stored[0].meta.source).toBe('promotion');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('OAD spec.brain.seeds config parsing', () => {
|
|
215
|
+
it('parses brain seed config from YAML', () => {
|
|
216
|
+
const yaml = require('js-yaml');
|
|
217
|
+
const config = yaml.load(`
|
|
218
|
+
apiVersion: opc/v1
|
|
219
|
+
kind: Agent
|
|
220
|
+
metadata:
|
|
221
|
+
name: test
|
|
222
|
+
spec:
|
|
223
|
+
brain:
|
|
224
|
+
seeds:
|
|
225
|
+
- brain-seeds/industry.md
|
|
226
|
+
- brain-seeds/job.md
|
|
227
|
+
- brain-seeds/workstation.md
|
|
228
|
+
autoSeed: true
|
|
229
|
+
evolve:
|
|
230
|
+
enabled: true
|
|
231
|
+
direction: bottom-up
|
|
232
|
+
`) as any;
|
|
233
|
+
|
|
234
|
+
expect(config.spec.brain.seeds).toHaveLength(3);
|
|
235
|
+
expect(config.spec.brain.autoSeed).toBe(true);
|
|
236
|
+
expect(config.spec.brain.evolve.enabled).toBe(true);
|
|
237
|
+
expect(config.spec.brain.evolve.direction).toBe('bottom-up');
|
|
238
|
+
});
|
|
239
|
+
});
|