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/src/core/node-network.ts
CHANGED
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
import { randomUUID } from 'crypto';
|
|
2
|
-
import { EventEmitter } from 'events';
|
|
3
|
-
|
|
4
|
-
export interface RemoteNode {
|
|
5
|
-
id: string;
|
|
6
|
-
name: string;
|
|
7
|
-
host: string;
|
|
8
|
-
port: number;
|
|
9
|
-
type: 'vps' | 'pi' | 'phone' | 'desktop';
|
|
10
|
-
status: 'online' | 'offline' | 'pairing';
|
|
11
|
-
capabilities: string[];
|
|
12
|
-
lastSeen: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class NodeNetwork extends EventEmitter {
|
|
16
|
-
private nodes = new Map<string, RemoteNode>();
|
|
17
|
-
|
|
18
|
-
addNode(config: Partial<RemoteNode>): RemoteNode {
|
|
19
|
-
const node: RemoteNode = {
|
|
20
|
-
id: config.id || randomUUID(),
|
|
21
|
-
name: config.name || 'unnamed',
|
|
22
|
-
host: config.host || 'localhost',
|
|
23
|
-
port: config.port || 8080,
|
|
24
|
-
type: config.type || 'desktop',
|
|
25
|
-
status: config.status || 'offline',
|
|
26
|
-
capabilities: config.capabilities || [],
|
|
27
|
-
lastSeen: Date.now(),
|
|
28
|
-
};
|
|
29
|
-
this.nodes.set(node.id, node);
|
|
30
|
-
return node;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
removeNode(id: string): void {
|
|
34
|
-
if (!this.nodes.has(id)) throw new Error(`Node ${id} not found`);
|
|
35
|
-
this.nodes.delete(id);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
listNodes(): RemoteNode[] {
|
|
39
|
-
return Array.from(this.nodes.values());
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getNode(id: string): RemoteNode | null {
|
|
43
|
-
return this.nodes.get(id) || null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async pair(nodeId: string, pairingCode: string): Promise<boolean> {
|
|
47
|
-
const node = this.nodes.get(nodeId);
|
|
48
|
-
if (!node) throw new Error(`Node ${nodeId} not found`);
|
|
49
|
-
if (!pairingCode || pairingCode.length < 4) return false;
|
|
50
|
-
node.status = 'online';
|
|
51
|
-
node.lastSeen = Date.now();
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async sendCommand(nodeId: string, command: string): Promise<any> {
|
|
56
|
-
const node = this.nodes.get(nodeId);
|
|
57
|
-
if (!node) throw new Error(`Node ${nodeId} not found`);
|
|
58
|
-
if (node.status !== 'online') throw new Error(`Node ${nodeId} is ${node.status}`);
|
|
59
|
-
node.lastSeen = Date.now();
|
|
60
|
-
// Stub: return command echo
|
|
61
|
-
return { nodeId, command, status: 'sent', timestamp: Date.now() };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async broadcast(command: string): Promise<Map<string, any>> {
|
|
65
|
-
const results = new Map<string, any>();
|
|
66
|
-
for (const [id, node] of this.nodes) {
|
|
67
|
-
if (node.status === 'online') {
|
|
68
|
-
try {
|
|
69
|
-
results.set(id, await this.sendCommand(id, command));
|
|
70
|
-
} catch (e: any) {
|
|
71
|
-
results.set(id, { error: e.message });
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return results;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async healthCheck(): Promise<Map<string, boolean>> {
|
|
79
|
-
const results = new Map<string, boolean>();
|
|
80
|
-
for (const [id, node] of this.nodes) {
|
|
81
|
-
results.set(id, node.status === 'online');
|
|
82
|
-
node.lastSeen = Date.now();
|
|
83
|
-
}
|
|
84
|
-
return results;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
|
|
4
|
+
export interface RemoteNode {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
host: string;
|
|
8
|
+
port: number;
|
|
9
|
+
type: 'vps' | 'pi' | 'phone' | 'desktop';
|
|
10
|
+
status: 'online' | 'offline' | 'pairing';
|
|
11
|
+
capabilities: string[];
|
|
12
|
+
lastSeen: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class NodeNetwork extends EventEmitter {
|
|
16
|
+
private nodes = new Map<string, RemoteNode>();
|
|
17
|
+
|
|
18
|
+
addNode(config: Partial<RemoteNode>): RemoteNode {
|
|
19
|
+
const node: RemoteNode = {
|
|
20
|
+
id: config.id || randomUUID(),
|
|
21
|
+
name: config.name || 'unnamed',
|
|
22
|
+
host: config.host || 'localhost',
|
|
23
|
+
port: config.port || 8080,
|
|
24
|
+
type: config.type || 'desktop',
|
|
25
|
+
status: config.status || 'offline',
|
|
26
|
+
capabilities: config.capabilities || [],
|
|
27
|
+
lastSeen: Date.now(),
|
|
28
|
+
};
|
|
29
|
+
this.nodes.set(node.id, node);
|
|
30
|
+
return node;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
removeNode(id: string): void {
|
|
34
|
+
if (!this.nodes.has(id)) throw new Error(`Node ${id} not found`);
|
|
35
|
+
this.nodes.delete(id);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
listNodes(): RemoteNode[] {
|
|
39
|
+
return Array.from(this.nodes.values());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getNode(id: string): RemoteNode | null {
|
|
43
|
+
return this.nodes.get(id) || null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async pair(nodeId: string, pairingCode: string): Promise<boolean> {
|
|
47
|
+
const node = this.nodes.get(nodeId);
|
|
48
|
+
if (!node) throw new Error(`Node ${nodeId} not found`);
|
|
49
|
+
if (!pairingCode || pairingCode.length < 4) return false;
|
|
50
|
+
node.status = 'online';
|
|
51
|
+
node.lastSeen = Date.now();
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async sendCommand(nodeId: string, command: string): Promise<any> {
|
|
56
|
+
const node = this.nodes.get(nodeId);
|
|
57
|
+
if (!node) throw new Error(`Node ${nodeId} not found`);
|
|
58
|
+
if (node.status !== 'online') throw new Error(`Node ${nodeId} is ${node.status}`);
|
|
59
|
+
node.lastSeen = Date.now();
|
|
60
|
+
// Stub: return command echo
|
|
61
|
+
return { nodeId, command, status: 'sent', timestamp: Date.now() };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async broadcast(command: string): Promise<Map<string, any>> {
|
|
65
|
+
const results = new Map<string, any>();
|
|
66
|
+
for (const [id, node] of this.nodes) {
|
|
67
|
+
if (node.status === 'online') {
|
|
68
|
+
try {
|
|
69
|
+
results.set(id, await this.sendCommand(id, command));
|
|
70
|
+
} catch (e: any) {
|
|
71
|
+
results.set(id, { error: e.message });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async healthCheck(): Promise<Map<string, boolean>> {
|
|
79
|
+
const results = new Map<string, boolean>();
|
|
80
|
+
for (const [id, node] of this.nodes) {
|
|
81
|
+
results.set(id, node.status === 'online');
|
|
82
|
+
node.lastSeen = Date.now();
|
|
83
|
+
}
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/core/profiles.ts
CHANGED
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import * as os from 'os';
|
|
4
|
-
|
|
5
|
-
// ─── Types ───────────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
export interface ProfileConfig {
|
|
8
|
-
model?: string;
|
|
9
|
-
systemPrompt?: string;
|
|
10
|
-
temperature?: number;
|
|
11
|
-
[key: string]: unknown;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface Profile {
|
|
15
|
-
name: string;
|
|
16
|
-
config: ProfileConfig;
|
|
17
|
-
createdAt: number;
|
|
18
|
-
lastUsed: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ─── ProfileManager ─────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
export class ProfileManager {
|
|
24
|
-
private profilesDir: string;
|
|
25
|
-
private currentName: string = 'default';
|
|
26
|
-
|
|
27
|
-
constructor(baseDir?: string) {
|
|
28
|
-
this.profilesDir = baseDir ?? path.join(os.homedir(), '.opc', 'profiles');
|
|
29
|
-
this.ensureDir(this.profilesDir);
|
|
30
|
-
|
|
31
|
-
// Load current profile pointer
|
|
32
|
-
const pointerFile = path.join(this.profilesDir, '.current');
|
|
33
|
-
if (fs.existsSync(pointerFile)) {
|
|
34
|
-
this.currentName = fs.readFileSync(pointerFile, 'utf-8').trim();
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
private ensureDir(dir: string): void {
|
|
39
|
-
if (!fs.existsSync(dir)) {
|
|
40
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
private profileDir(name: string): string {
|
|
45
|
-
return path.join(this.profilesDir, name);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private profileFile(name: string): string {
|
|
49
|
-
return path.join(this.profileDir(name), 'profile.json');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
create(name: string, config: ProfileConfig = {}): Profile {
|
|
53
|
-
const dir = this.profileDir(name);
|
|
54
|
-
if (fs.existsSync(dir)) {
|
|
55
|
-
throw new Error(`Profile '${name}' already exists`);
|
|
56
|
-
}
|
|
57
|
-
this.ensureDir(dir);
|
|
58
|
-
// Also create memory and sessions dirs
|
|
59
|
-
this.ensureDir(path.join(dir, 'memory'));
|
|
60
|
-
this.ensureDir(path.join(dir, 'sessions'));
|
|
61
|
-
|
|
62
|
-
const profile: Profile = {
|
|
63
|
-
name,
|
|
64
|
-
config,
|
|
65
|
-
createdAt: Date.now(),
|
|
66
|
-
lastUsed: Date.now(),
|
|
67
|
-
};
|
|
68
|
-
fs.writeFileSync(this.profileFile(name), JSON.stringify(profile, null, 2));
|
|
69
|
-
return profile;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
switch(name: string): void {
|
|
73
|
-
if (!fs.existsSync(this.profileDir(name))) {
|
|
74
|
-
throw new Error(`Profile '${name}' does not exist`);
|
|
75
|
-
}
|
|
76
|
-
this.currentName = name;
|
|
77
|
-
fs.writeFileSync(path.join(this.profilesDir, '.current'), name);
|
|
78
|
-
|
|
79
|
-
// Update lastUsed
|
|
80
|
-
const profile = this.get(name);
|
|
81
|
-
profile.lastUsed = Date.now();
|
|
82
|
-
fs.writeFileSync(this.profileFile(name), JSON.stringify(profile, null, 2));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
list(): Profile[] {
|
|
86
|
-
if (!fs.existsSync(this.profilesDir)) return [];
|
|
87
|
-
return fs.readdirSync(this.profilesDir)
|
|
88
|
-
.filter(f => {
|
|
89
|
-
const full = path.join(this.profilesDir, f);
|
|
90
|
-
return fs.statSync(full).isDirectory() && fs.existsSync(path.join(full, 'profile.json'));
|
|
91
|
-
})
|
|
92
|
-
.map(f => this.get(f));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
get(name: string): Profile {
|
|
96
|
-
const file = this.profileFile(name);
|
|
97
|
-
if (!fs.existsSync(file)) {
|
|
98
|
-
throw new Error(`Profile '${name}' not found`);
|
|
99
|
-
}
|
|
100
|
-
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
delete(name: string): void {
|
|
104
|
-
if (name === this.currentName) {
|
|
105
|
-
throw new Error(`Cannot delete the current active profile '${name}'`);
|
|
106
|
-
}
|
|
107
|
-
const dir = this.profileDir(name);
|
|
108
|
-
if (!fs.existsSync(dir)) {
|
|
109
|
-
throw new Error(`Profile '${name}' does not exist`);
|
|
110
|
-
}
|
|
111
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
current(): Profile {
|
|
115
|
-
try {
|
|
116
|
-
return this.get(this.currentName);
|
|
117
|
-
} catch {
|
|
118
|
-
// Auto-create default
|
|
119
|
-
return this.create(this.currentName);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
|
|
5
|
+
// ─── Types ───────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export interface ProfileConfig {
|
|
8
|
+
model?: string;
|
|
9
|
+
systemPrompt?: string;
|
|
10
|
+
temperature?: number;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Profile {
|
|
15
|
+
name: string;
|
|
16
|
+
config: ProfileConfig;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
lastUsed: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ─── ProfileManager ─────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export class ProfileManager {
|
|
24
|
+
private profilesDir: string;
|
|
25
|
+
private currentName: string = 'default';
|
|
26
|
+
|
|
27
|
+
constructor(baseDir?: string) {
|
|
28
|
+
this.profilesDir = baseDir ?? path.join(os.homedir(), '.opc', 'profiles');
|
|
29
|
+
this.ensureDir(this.profilesDir);
|
|
30
|
+
|
|
31
|
+
// Load current profile pointer
|
|
32
|
+
const pointerFile = path.join(this.profilesDir, '.current');
|
|
33
|
+
if (fs.existsSync(pointerFile)) {
|
|
34
|
+
this.currentName = fs.readFileSync(pointerFile, 'utf-8').trim();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private ensureDir(dir: string): void {
|
|
39
|
+
if (!fs.existsSync(dir)) {
|
|
40
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private profileDir(name: string): string {
|
|
45
|
+
return path.join(this.profilesDir, name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private profileFile(name: string): string {
|
|
49
|
+
return path.join(this.profileDir(name), 'profile.json');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
create(name: string, config: ProfileConfig = {}): Profile {
|
|
53
|
+
const dir = this.profileDir(name);
|
|
54
|
+
if (fs.existsSync(dir)) {
|
|
55
|
+
throw new Error(`Profile '${name}' already exists`);
|
|
56
|
+
}
|
|
57
|
+
this.ensureDir(dir);
|
|
58
|
+
// Also create memory and sessions dirs
|
|
59
|
+
this.ensureDir(path.join(dir, 'memory'));
|
|
60
|
+
this.ensureDir(path.join(dir, 'sessions'));
|
|
61
|
+
|
|
62
|
+
const profile: Profile = {
|
|
63
|
+
name,
|
|
64
|
+
config,
|
|
65
|
+
createdAt: Date.now(),
|
|
66
|
+
lastUsed: Date.now(),
|
|
67
|
+
};
|
|
68
|
+
fs.writeFileSync(this.profileFile(name), JSON.stringify(profile, null, 2));
|
|
69
|
+
return profile;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
switch(name: string): void {
|
|
73
|
+
if (!fs.existsSync(this.profileDir(name))) {
|
|
74
|
+
throw new Error(`Profile '${name}' does not exist`);
|
|
75
|
+
}
|
|
76
|
+
this.currentName = name;
|
|
77
|
+
fs.writeFileSync(path.join(this.profilesDir, '.current'), name);
|
|
78
|
+
|
|
79
|
+
// Update lastUsed
|
|
80
|
+
const profile = this.get(name);
|
|
81
|
+
profile.lastUsed = Date.now();
|
|
82
|
+
fs.writeFileSync(this.profileFile(name), JSON.stringify(profile, null, 2));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
list(): Profile[] {
|
|
86
|
+
if (!fs.existsSync(this.profilesDir)) return [];
|
|
87
|
+
return fs.readdirSync(this.profilesDir)
|
|
88
|
+
.filter(f => {
|
|
89
|
+
const full = path.join(this.profilesDir, f);
|
|
90
|
+
return fs.statSync(full).isDirectory() && fs.existsSync(path.join(full, 'profile.json'));
|
|
91
|
+
})
|
|
92
|
+
.map(f => this.get(f));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get(name: string): Profile {
|
|
96
|
+
const file = this.profileFile(name);
|
|
97
|
+
if (!fs.existsSync(file)) {
|
|
98
|
+
throw new Error(`Profile '${name}' not found`);
|
|
99
|
+
}
|
|
100
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
delete(name: string): void {
|
|
104
|
+
if (name === this.currentName) {
|
|
105
|
+
throw new Error(`Cannot delete the current active profile '${name}'`);
|
|
106
|
+
}
|
|
107
|
+
const dir = this.profileDir(name);
|
|
108
|
+
if (!fs.existsSync(dir)) {
|
|
109
|
+
throw new Error(`Profile '${name}' does not exist`);
|
|
110
|
+
}
|
|
111
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
current(): Profile {
|
|
115
|
+
try {
|
|
116
|
+
return this.get(this.currentName);
|
|
117
|
+
} catch {
|
|
118
|
+
// Auto-create default
|
|
119
|
+
return this.create(this.currentName);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
package/src/core/runtime.ts
CHANGED
|
@@ -45,7 +45,16 @@ export class AgentRuntime {
|
|
|
45
45
|
|
|
46
46
|
async loadConfig(filePath: string): Promise<OADDocument> {
|
|
47
47
|
const fs = require('fs');
|
|
48
|
+
const path = require('path');
|
|
49
|
+
|
|
50
|
+
// 如果指定文件不存在,尝试 fallback
|
|
48
51
|
if (!fs.existsSync(filePath)) {
|
|
52
|
+
// 如果发现旧的 agent.yaml,提示迁移
|
|
53
|
+
if (filePath === 'oad.yaml' && fs.existsSync('agent.yaml')) {
|
|
54
|
+
this.logger.warn('⚠️ 发现 agent.yaml 但未找到 oad.yaml。建议运行 `opc migrate` 统一为 oad.yaml。');
|
|
55
|
+
this.logger.info('暂时使用 agent.yaml 加载配置...');
|
|
56
|
+
filePath = 'agent.yaml';
|
|
57
|
+
} else {
|
|
49
58
|
// Auto-create a minimal oad.yaml with auto-detect provider
|
|
50
59
|
const yaml = require('js-yaml');
|
|
51
60
|
const defaultOAD = {
|
|
@@ -61,9 +70,16 @@ export class AgentRuntime {
|
|
|
61
70
|
};
|
|
62
71
|
fs.writeFileSync(filePath, yaml.dump(defaultOAD, { lineWidth: 120 }));
|
|
63
72
|
this.logger.info('Created default oad.yaml (no config file found)');
|
|
73
|
+
}
|
|
64
74
|
}
|
|
65
75
|
this.config = loadOAD(filePath);
|
|
66
76
|
this.logger.info('Config loaded', { name: this.config.metadata.name });
|
|
77
|
+
|
|
78
|
+
// 如果同时存在 agent.yaml 和 oad.yaml,提示用户清理
|
|
79
|
+
if (fs.existsSync('agent.yaml') && fs.existsSync('oad.yaml')) {
|
|
80
|
+
this.logger.warn('⚠️ 同时存在 agent.yaml 和 oad.yaml。建议删除 agent.yaml,统一使用 oad.yaml。');
|
|
81
|
+
}
|
|
82
|
+
|
|
67
83
|
return this.config;
|
|
68
84
|
}
|
|
69
85
|
|
|
@@ -99,6 +115,15 @@ export class AgentRuntime {
|
|
|
99
115
|
const cfg = config ?? this.config;
|
|
100
116
|
if (!cfg) throw new Error('No config loaded. Call loadConfig() first.');
|
|
101
117
|
|
|
118
|
+
// 检查 API key 是否为占位符,启动时警告
|
|
119
|
+
const apiKey = process.env.OPC_LLM_API_KEY;
|
|
120
|
+
const cfgProvider = cfg.spec.provider?.default;
|
|
121
|
+
if (cfgProvider !== 'ollama' && cfgProvider !== 'auto') {
|
|
122
|
+
if (!apiKey || apiKey === 'your-api-key-here') {
|
|
123
|
+
this.logger.warn('⚠️ API Key 未配置或仍是占位符。请编辑 .env 文件设置 OPC_LLM_API_KEY。');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
102
127
|
let memory: MemoryStore | undefined;
|
|
103
128
|
const memCfg = cfg.spec.memory;
|
|
104
129
|
if (memCfg && typeof memCfg.longTerm === 'object' && memCfg.longTerm.provider === 'deepbrain') {
|