opc-agent 3.0.1 → 4.0.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/README.md +404 -74
- package/README.zh-CN.md +82 -0
- package/dist/channels/dingtalk.d.ts +17 -0
- package/dist/channels/dingtalk.js +38 -0
- package/dist/channels/googlechat.d.ts +14 -0
- package/dist/channels/googlechat.js +37 -0
- package/dist/channels/imessage.d.ts +13 -0
- package/dist/channels/imessage.js +28 -0
- package/dist/channels/irc.d.ts +20 -0
- package/dist/channels/irc.js +71 -0
- package/dist/channels/line.d.ts +14 -0
- package/dist/channels/line.js +28 -0
- package/dist/channels/matrix.d.ts +15 -0
- package/dist/channels/matrix.js +28 -0
- package/dist/channels/mattermost.d.ts +18 -0
- package/dist/channels/mattermost.js +49 -0
- package/dist/channels/msteams.d.ts +14 -0
- package/dist/channels/msteams.js +28 -0
- package/dist/channels/nostr.d.ts +14 -0
- package/dist/channels/nostr.js +28 -0
- package/dist/channels/qq.d.ts +15 -0
- package/dist/channels/qq.js +28 -0
- package/dist/channels/signal.d.ts +14 -0
- package/dist/channels/signal.js +28 -0
- package/dist/channels/sms.d.ts +15 -0
- package/dist/channels/sms.js +28 -0
- package/dist/channels/twitch.d.ts +17 -0
- package/dist/channels/twitch.js +59 -0
- package/dist/channels/voice-call.d.ts +27 -0
- package/dist/channels/voice-call.js +82 -0
- package/dist/channels/whatsapp.d.ts +14 -0
- package/dist/channels/whatsapp.js +28 -0
- package/dist/cli/chat.d.ts +2 -0
- package/dist/cli/chat.js +134 -0
- package/dist/cli/setup.d.ts +4 -0
- package/dist/cli/setup.js +303 -0
- package/dist/cli.js +142 -6
- package/dist/core/api-server.d.ts +25 -0
- package/dist/core/api-server.js +286 -0
- package/dist/core/audio.d.ts +50 -0
- package/dist/core/audio.js +68 -0
- package/dist/core/context-discovery.d.ts +16 -0
- package/dist/core/context-discovery.js +107 -0
- package/dist/core/context-refs.d.ts +29 -0
- package/dist/core/context-refs.js +162 -0
- package/dist/core/gateway.d.ts +53 -0
- package/dist/core/gateway.js +80 -0
- package/dist/core/heartbeat.d.ts +19 -0
- package/dist/core/heartbeat.js +50 -0
- package/dist/core/hooks.d.ts +28 -0
- package/dist/core/hooks.js +82 -0
- package/dist/core/ide-bridge.d.ts +53 -0
- package/dist/core/ide-bridge.js +97 -0
- package/dist/core/node-network.d.ts +23 -0
- package/dist/core/node-network.js +77 -0
- package/dist/core/profiles.d.ts +27 -0
- package/dist/core/profiles.js +131 -0
- package/dist/core/sandbox.d.ts +25 -0
- package/dist/core/sandbox.js +84 -1
- package/dist/core/session-manager.d.ts +33 -0
- package/dist/core/session-manager.js +157 -0
- package/dist/core/vision.d.ts +45 -0
- package/dist/core/vision.js +177 -0
- package/dist/hub/brain-seed.d.ts +14 -0
- package/dist/hub/brain-seed.js +77 -0
- package/dist/hub/client.d.ts +25 -0
- package/dist/hub/client.js +44 -0
- package/dist/index.d.ts +66 -1
- package/dist/index.js +95 -3
- package/dist/memory/context-compressor.d.ts +43 -0
- package/dist/memory/context-compressor.js +167 -0
- package/dist/memory/index.d.ts +4 -0
- package/dist/memory/index.js +5 -1
- package/dist/memory/user-profiler.d.ts +50 -0
- package/dist/memory/user-profiler.js +201 -0
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +54 -1
- package/dist/scheduler/cron-engine.d.ts +41 -0
- package/dist/scheduler/cron-engine.js +200 -0
- package/dist/scheduler/index.d.ts +3 -0
- package/dist/scheduler/index.js +7 -0
- package/dist/schema/oad.d.ts +12 -12
- package/dist/security/approvals.d.ts +53 -0
- package/dist/security/approvals.js +115 -0
- package/dist/security/elevated.d.ts +41 -0
- package/dist/security/elevated.js +89 -0
- package/dist/security/index.d.ts +6 -0
- package/dist/security/index.js +7 -1
- package/dist/security/secrets.d.ts +34 -0
- package/dist/security/secrets.js +115 -0
- package/dist/skills/builtin/index.d.ts +6 -0
- package/dist/skills/builtin/index.js +402 -0
- package/dist/skills/marketplace.d.ts +30 -0
- package/dist/skills/marketplace.js +142 -0
- package/dist/skills/types.d.ts +34 -0
- package/dist/skills/types.js +16 -0
- package/dist/studio/server.d.ts +25 -0
- package/dist/studio/server.js +780 -0
- package/dist/studio/templates-data.d.ts +21 -0
- package/dist/studio/templates-data.js +148 -0
- package/dist/studio-ui/index.html +2502 -1073
- package/dist/tools/builtin/browser.d.ts +47 -0
- package/dist/tools/builtin/browser.js +284 -0
- package/dist/tools/builtin/home-assistant.d.ts +12 -0
- package/dist/tools/builtin/home-assistant.js +126 -0
- package/dist/tools/builtin/index.d.ts +7 -1
- package/dist/tools/builtin/index.js +23 -2
- package/dist/tools/builtin/rl-tools.d.ts +13 -0
- package/dist/tools/builtin/rl-tools.js +228 -0
- package/dist/tools/builtin/vision.d.ts +6 -0
- package/dist/tools/builtin/vision.js +61 -0
- package/dist/tools/builtin/web-search.d.ts +9 -0
- package/dist/tools/builtin/web-search.js +150 -0
- package/dist/tools/document-processor.d.ts +39 -0
- package/dist/tools/document-processor.js +188 -0
- package/dist/tools/image-generator.d.ts +42 -0
- package/dist/tools/image-generator.js +136 -0
- package/dist/tools/web-scraper.d.ts +20 -0
- package/dist/tools/web-scraper.js +148 -0
- package/dist/tools/web-search.d.ts +51 -0
- package/dist/tools/web-search.js +152 -0
- package/install.ps1 +154 -0
- package/install.sh +164 -0
- package/package.json +63 -52
- package/src/channels/dingtalk.ts +46 -0
- package/src/channels/googlechat.ts +42 -0
- package/src/channels/imessage.ts +32 -0
- package/src/channels/irc.ts +82 -0
- package/src/channels/line.ts +33 -0
- package/src/channels/matrix.ts +34 -0
- package/src/channels/mattermost.ts +57 -0
- package/src/channels/msteams.ts +33 -0
- package/src/channels/nostr.ts +33 -0
- package/src/channels/qq.ts +34 -0
- package/src/channels/signal.ts +33 -0
- package/src/channels/sms.ts +34 -0
- package/src/channels/twitch.ts +65 -0
- package/src/channels/voice-call.ts +100 -0
- package/src/channels/whatsapp.ts +33 -0
- package/src/cli/chat.ts +99 -0
- package/src/cli/setup.ts +314 -0
- package/src/cli.ts +148 -6
- package/src/core/api-server.ts +277 -0
- package/src/core/audio.ts +98 -0
- package/src/core/context-discovery.ts +85 -0
- package/src/core/context-refs.ts +140 -0
- package/src/core/gateway.ts +106 -0
- package/src/core/heartbeat.ts +51 -0
- package/src/core/hooks.ts +105 -0
- package/src/core/ide-bridge.ts +133 -0
- package/src/core/node-network.ts +86 -0
- package/src/core/profiles.ts +122 -0
- package/src/core/sandbox.ts +100 -0
- package/src/core/session-manager.ts +137 -0
- package/src/core/vision.ts +180 -0
- package/src/hub/brain-seed.ts +54 -0
- package/src/hub/client.ts +60 -0
- package/src/index.ts +86 -1
- package/src/memory/context-compressor.ts +189 -0
- package/src/memory/index.ts +4 -0
- package/src/memory/user-profiler.ts +215 -0
- package/src/providers/index.ts +64 -1
- package/src/scheduler/cron-engine.ts +191 -0
- package/src/scheduler/index.ts +2 -0
- package/src/security/approvals.ts +143 -0
- package/src/security/elevated.ts +105 -0
- package/src/security/index.ts +6 -0
- package/src/security/secrets.ts +129 -0
- package/src/skills/builtin/index.ts +408 -0
- package/src/skills/marketplace.ts +113 -0
- package/src/skills/types.ts +42 -0
- package/src/studio/server.ts +1591 -791
- package/src/studio/templates-data.ts +178 -0
- package/src/studio-ui/index.html +2502 -1073
- package/src/tools/builtin/browser.ts +299 -0
- package/src/tools/builtin/home-assistant.ts +116 -0
- package/src/tools/builtin/index.ts +37 -28
- package/src/tools/builtin/rl-tools.ts +243 -0
- package/src/tools/builtin/vision.ts +64 -0
- package/src/tools/builtin/web-search.ts +126 -0
- package/src/tools/document-processor.ts +213 -0
- package/src/tools/image-generator.ts +150 -0
- package/src/tools/web-scraper.ts +179 -0
- package/src/tools/web-search.ts +180 -0
- package/tests/api-server.test.ts +148 -0
- package/tests/approvals.test.ts +89 -0
- package/tests/audio.test.ts +40 -0
- package/tests/browser.test.ts +179 -0
- package/tests/builtin-tools.test.ts +83 -83
- package/tests/channels-extra.test.ts +45 -0
- package/tests/context-compressor.test.ts +172 -0
- package/tests/context-refs.test.ts +121 -0
- package/tests/cron-engine.test.ts +101 -0
- package/tests/document-processor.test.ts +69 -0
- package/tests/e2e-nocode.test.ts +442 -0
- package/tests/elevated.test.ts +69 -0
- package/tests/gateway.test.ts +63 -71
- package/tests/home-assistant.test.ts +40 -0
- package/tests/hooks.test.ts +79 -0
- package/tests/ide-bridge.test.ts +38 -0
- package/tests/image-generator.test.ts +84 -0
- package/tests/node-network.test.ts +74 -0
- package/tests/profiles.test.ts +61 -0
- package/tests/rl-tools.test.ts +93 -0
- package/tests/sandbox-manager.test.ts +46 -0
- package/tests/secrets.test.ts +107 -0
- package/tests/settings-api.test.ts +148 -0
- package/tests/setup.test.ts +73 -0
- package/tests/studio.test.ts +402 -229
- package/tests/tools/builtin-extended.test.ts +138 -138
- package/tests/user-profiler.test.ts +169 -0
- package/tests/v090-features.test.ts +254 -0
- package/tests/vision.test.ts +61 -0
- package/tests/voice-call.test.ts +47 -0
- package/tests/voice-interaction.test.ts +38 -0
- package/tests/web-search.test.ts +155 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContextCompressor = void 0;
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
maxTokens: 8000,
|
|
6
|
+
compressThreshold: 0.8,
|
|
7
|
+
preserveRecent: 10,
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Context compression with optional DeepBrain memory offloading.
|
|
11
|
+
*/
|
|
12
|
+
class ContextCompressor {
|
|
13
|
+
config;
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Estimate token count using language-aware heuristic.
|
|
19
|
+
* English: ~1 token per 4 chars. Chinese: ~1 token per 2 chars.
|
|
20
|
+
*/
|
|
21
|
+
estimateTokens(text) {
|
|
22
|
+
let tokens = 0;
|
|
23
|
+
for (const char of text) {
|
|
24
|
+
// CJK Unicode range detection
|
|
25
|
+
const code = char.codePointAt(0) ?? 0;
|
|
26
|
+
if ((code >= 0x4e00 && code <= 0x9fff) || // CJK Unified
|
|
27
|
+
(code >= 0x3400 && code <= 0x4dbf) || // CJK Extension A
|
|
28
|
+
(code >= 0x3000 && code <= 0x303f) // CJK Punctuation
|
|
29
|
+
) {
|
|
30
|
+
tokens += 0.5; // 1 token per 2 chars
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
tokens += 0.25; // 1 token per 4 chars
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Math.ceil(tokens);
|
|
37
|
+
}
|
|
38
|
+
estimateMessagesTokens(messages) {
|
|
39
|
+
return messages.reduce((sum, m) => sum + this.estimateTokens(m.content), 0);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract key insights from messages for brain storage.
|
|
43
|
+
*/
|
|
44
|
+
extractInsights(messages) {
|
|
45
|
+
const insights = [];
|
|
46
|
+
for (const msg of messages) {
|
|
47
|
+
const c = msg.content;
|
|
48
|
+
// Decisions
|
|
49
|
+
if (/\b(decided|decision|choose|chose|will use|going with|let's go|确定|决定)\b/i.test(c)) {
|
|
50
|
+
insights.push({ content: c.slice(0, 500), type: 'decision' });
|
|
51
|
+
}
|
|
52
|
+
// Facts / definitions
|
|
53
|
+
else if (/\b(is defined as|means|equals|refers to|是指|定义)\b/i.test(c)) {
|
|
54
|
+
insights.push({ content: c.slice(0, 500), type: 'fact' });
|
|
55
|
+
}
|
|
56
|
+
// Preferences
|
|
57
|
+
else if (/\b(prefer|like|want|don't like|不喜欢|喜欢|偏好)\b/i.test(c)) {
|
|
58
|
+
insights.push({ content: c.slice(0, 500), type: 'preference' });
|
|
59
|
+
}
|
|
60
|
+
// Code snippets
|
|
61
|
+
else if (/```[\s\S]{20,}```/.test(c)) {
|
|
62
|
+
insights.push({ content: c.slice(0, 800), type: 'code' });
|
|
63
|
+
}
|
|
64
|
+
// Long assistant messages likely contain useful info
|
|
65
|
+
else if (msg.role === 'assistant' && c.length > 200) {
|
|
66
|
+
insights.push({ content: c.slice(0, 500), type: 'knowledge' });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return insights;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Generate a simple summary from messages (no-brain fallback).
|
|
73
|
+
*/
|
|
74
|
+
summarize(messages) {
|
|
75
|
+
const topics = new Set();
|
|
76
|
+
const keyLines = [];
|
|
77
|
+
for (const msg of messages) {
|
|
78
|
+
// Extract first meaningful sentence
|
|
79
|
+
const firstLine = msg.content.split(/[.\n!?。!?]/)[0]?.trim();
|
|
80
|
+
if (firstLine && firstLine.length > 10 && firstLine.length < 200) {
|
|
81
|
+
if (keyLines.length < 5)
|
|
82
|
+
keyLines.push(`[${msg.role}] ${firstLine}`);
|
|
83
|
+
}
|
|
84
|
+
// Extract topic words (capitalized words, Chinese phrases)
|
|
85
|
+
const words = msg.content.match(/[A-Z][a-z]{2,}/g) ?? [];
|
|
86
|
+
words.forEach(w => topics.add(w));
|
|
87
|
+
}
|
|
88
|
+
const topicStr = [...topics].slice(0, 10).join(', ');
|
|
89
|
+
const linesStr = keyLines.join('; ');
|
|
90
|
+
return `Topics: ${topicStr || 'general discussion'}. Key points: ${linesStr || 'varied conversation'}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Compress messages when token count exceeds threshold.
|
|
94
|
+
*/
|
|
95
|
+
async compress(messages, config) {
|
|
96
|
+
const cfg = { ...this.config, ...config };
|
|
97
|
+
const totalTokens = this.estimateMessagesTokens(messages);
|
|
98
|
+
const threshold = cfg.maxTokens * cfg.compressThreshold;
|
|
99
|
+
// Under threshold — return as-is
|
|
100
|
+
if (totalTokens <= threshold) {
|
|
101
|
+
return {
|
|
102
|
+
messages: [...messages],
|
|
103
|
+
learnedCount: 0,
|
|
104
|
+
savedTokens: 0,
|
|
105
|
+
summary: '',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const recentCount = Math.min(cfg.preserveRecent, messages.length);
|
|
109
|
+
const splitIdx = messages.length - recentCount;
|
|
110
|
+
const oldMessages = messages.slice(0, splitIdx);
|
|
111
|
+
const recentMessages = messages.slice(splitIdx);
|
|
112
|
+
if (oldMessages.length === 0) {
|
|
113
|
+
return { messages: [...messages], learnedCount: 0, savedTokens: 0, summary: '' };
|
|
114
|
+
}
|
|
115
|
+
const oldTokens = this.estimateMessagesTokens(oldMessages);
|
|
116
|
+
let learnedCount = 0;
|
|
117
|
+
let summary;
|
|
118
|
+
if (cfg.brain) {
|
|
119
|
+
// Extract and learn insights
|
|
120
|
+
const insights = this.extractInsights(oldMessages);
|
|
121
|
+
for (const insight of insights) {
|
|
122
|
+
try {
|
|
123
|
+
await cfg.brain.learn(insight.content, { insight_type: insight.type });
|
|
124
|
+
learnedCount++;
|
|
125
|
+
}
|
|
126
|
+
catch { /* non-critical */ }
|
|
127
|
+
}
|
|
128
|
+
summary = `${oldMessages.length} messages compressed. Extracted ${learnedCount} insights (${insights.map(i => i.type).filter((v, i, a) => a.indexOf(v) === i).join(', ')}).`;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
summary = this.summarize(oldMessages);
|
|
132
|
+
}
|
|
133
|
+
const compressionMessage = {
|
|
134
|
+
id: `compressed-${Date.now()}`,
|
|
135
|
+
role: 'system',
|
|
136
|
+
content: `[Context compressed: ${oldMessages.length} messages → ${summary}${cfg.brain ? ' Details stored in Brain, use recall() to retrieve.' : ''}]`,
|
|
137
|
+
timestamp: Date.now(),
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
messages: [compressionMessage, ...recentMessages],
|
|
141
|
+
learnedCount,
|
|
142
|
+
savedTokens: oldTokens - this.estimateTokens(compressionMessage.content),
|
|
143
|
+
summary,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Restore context from brain for a given query.
|
|
148
|
+
*/
|
|
149
|
+
async restore(query, brain) {
|
|
150
|
+
if (!brain?.recall)
|
|
151
|
+
return [];
|
|
152
|
+
try {
|
|
153
|
+
const results = await brain.recall(query);
|
|
154
|
+
if (Array.isArray(results)) {
|
|
155
|
+
return results.map((r) => typeof r === 'string' ? r : r.content ?? JSON.stringify(r));
|
|
156
|
+
}
|
|
157
|
+
if (typeof results === 'string')
|
|
158
|
+
return [results];
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.ContextCompressor = ContextCompressor;
|
|
167
|
+
//# sourceMappingURL=context-compressor.js.map
|
package/dist/memory/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Message, MemoryStore } from '../core/types';
|
|
2
2
|
export { BrainSeedLoader, KnowledgeEvolver } from './seed-loader';
|
|
3
3
|
export type { BrainSeedConfig, SeedPage, SeedResult, PromotionResult, PromotionCandidate } from './seed-loader';
|
|
4
|
+
export { ContextCompressor } from './context-compressor';
|
|
5
|
+
export type { CompressorConfig, CompressResult } from './context-compressor';
|
|
6
|
+
export { UserProfiler } from './user-profiler';
|
|
7
|
+
export type { UserProfile } from './user-profiler';
|
|
4
8
|
export declare class InMemoryStore implements MemoryStore {
|
|
5
9
|
private store;
|
|
6
10
|
private conversations;
|
package/dist/memory/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InMemoryStore = exports.KnowledgeEvolver = exports.BrainSeedLoader = void 0;
|
|
3
|
+
exports.InMemoryStore = exports.UserProfiler = exports.ContextCompressor = exports.KnowledgeEvolver = exports.BrainSeedLoader = void 0;
|
|
4
4
|
var seed_loader_1 = require("./seed-loader");
|
|
5
5
|
Object.defineProperty(exports, "BrainSeedLoader", { enumerable: true, get: function () { return seed_loader_1.BrainSeedLoader; } });
|
|
6
6
|
Object.defineProperty(exports, "KnowledgeEvolver", { enumerable: true, get: function () { return seed_loader_1.KnowledgeEvolver; } });
|
|
7
|
+
var context_compressor_1 = require("./context-compressor");
|
|
8
|
+
Object.defineProperty(exports, "ContextCompressor", { enumerable: true, get: function () { return context_compressor_1.ContextCompressor; } });
|
|
9
|
+
var user_profiler_1 = require("./user-profiler");
|
|
10
|
+
Object.defineProperty(exports, "UserProfiler", { enumerable: true, get: function () { return user_profiler_1.UserProfiler; } });
|
|
7
11
|
class InMemoryStore {
|
|
8
12
|
store = new Map();
|
|
9
13
|
conversations = new Map();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Message } from '../core/types';
|
|
2
|
+
export interface UserProfile {
|
|
3
|
+
preferences: Record<string, string>;
|
|
4
|
+
communication_style: string;
|
|
5
|
+
expertise_areas: string[];
|
|
6
|
+
common_requests: string[];
|
|
7
|
+
last_updated: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* DeepBrain-powered user profiling from conversation signals.
|
|
11
|
+
*/
|
|
12
|
+
export declare class UserProfiler {
|
|
13
|
+
private observationCount;
|
|
14
|
+
private signals;
|
|
15
|
+
private readonly LEARN_INTERVAL;
|
|
16
|
+
/**
|
|
17
|
+
* Detect language mix of a text.
|
|
18
|
+
*/
|
|
19
|
+
private detectLanguage;
|
|
20
|
+
/**
|
|
21
|
+
* Detect technical level from content.
|
|
22
|
+
*/
|
|
23
|
+
private detectTechLevel;
|
|
24
|
+
/**
|
|
25
|
+
* Detect communication style.
|
|
26
|
+
*/
|
|
27
|
+
private detectStyle;
|
|
28
|
+
/**
|
|
29
|
+
* Extract domain keywords.
|
|
30
|
+
*/
|
|
31
|
+
private extractDomainKeywords;
|
|
32
|
+
/**
|
|
33
|
+
* Classify request type.
|
|
34
|
+
*/
|
|
35
|
+
private classifyRequest;
|
|
36
|
+
/**
|
|
37
|
+
* Observe a user message and accumulate profile signals.
|
|
38
|
+
*/
|
|
39
|
+
observe(message: Message, brain?: any): Promise<void>;
|
|
40
|
+
private buildProfileFromSignals;
|
|
41
|
+
/**
|
|
42
|
+
* Get user profile, optionally from brain recall.
|
|
43
|
+
*/
|
|
44
|
+
getProfile(brain?: any): Promise<UserProfile>;
|
|
45
|
+
/**
|
|
46
|
+
* Enhance a system prompt with user profile context.
|
|
47
|
+
*/
|
|
48
|
+
enhance(systemPrompt: string, profile: UserProfile): string;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=user-profiler.d.ts.map
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserProfiler = void 0;
|
|
4
|
+
const EMPTY_PROFILE = {
|
|
5
|
+
preferences: {},
|
|
6
|
+
communication_style: 'unknown',
|
|
7
|
+
expertise_areas: [],
|
|
8
|
+
common_requests: [],
|
|
9
|
+
last_updated: 0,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* DeepBrain-powered user profiling from conversation signals.
|
|
13
|
+
*/
|
|
14
|
+
class UserProfiler {
|
|
15
|
+
observationCount = 0;
|
|
16
|
+
signals = {
|
|
17
|
+
languages: new Map(),
|
|
18
|
+
techKeywords: new Set(),
|
|
19
|
+
styleSignals: { brief: 0, detailed: 0, formal: 0, casual: 0 },
|
|
20
|
+
requestTypes: new Map(),
|
|
21
|
+
};
|
|
22
|
+
LEARN_INTERVAL = 20;
|
|
23
|
+
/**
|
|
24
|
+
* Detect language mix of a text.
|
|
25
|
+
*/
|
|
26
|
+
detectLanguage(text) {
|
|
27
|
+
let cn = 0, en = 0;
|
|
28
|
+
for (const char of text) {
|
|
29
|
+
const code = char.codePointAt(0) ?? 0;
|
|
30
|
+
if (code >= 0x4e00 && code <= 0x9fff)
|
|
31
|
+
cn++;
|
|
32
|
+
else if ((code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a))
|
|
33
|
+
en++;
|
|
34
|
+
}
|
|
35
|
+
if (cn > 0 && en > 0)
|
|
36
|
+
return 'mixed';
|
|
37
|
+
if (cn > en)
|
|
38
|
+
return 'chinese';
|
|
39
|
+
return 'english';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Detect technical level from content.
|
|
43
|
+
*/
|
|
44
|
+
detectTechLevel(text) {
|
|
45
|
+
const expertTerms = /\b(kubernetes|k8s|microservice|architecture|distributed|consensus|raft|paxos|sharding|vector db|embedding|fine-?tun|transformer|CUDA|inference|quantiz|LoRA|RAG)\b/i;
|
|
46
|
+
const intermediateTerms = /\b(API|REST|GraphQL|Docker|CI\/CD|database|deploy|cloud|React|TypeScript|Python|async|cache|redis|nginx)\b/i;
|
|
47
|
+
if (expertTerms.test(text))
|
|
48
|
+
return 'expert';
|
|
49
|
+
if (intermediateTerms.test(text))
|
|
50
|
+
return 'intermediate';
|
|
51
|
+
return 'beginner';
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Detect communication style.
|
|
55
|
+
*/
|
|
56
|
+
detectStyle(text) {
|
|
57
|
+
if (text.length < 50)
|
|
58
|
+
this.signals.styleSignals.brief++;
|
|
59
|
+
else if (text.length > 300)
|
|
60
|
+
this.signals.styleSignals.detailed++;
|
|
61
|
+
if (/\b(please|kindly|would you|could you|请问|烦请|麻烦)\b/i.test(text)) {
|
|
62
|
+
this.signals.styleSignals.formal++;
|
|
63
|
+
}
|
|
64
|
+
if (/[!]{2,}|lol|haha|😂|🤣|哈哈|牛|666|👍/i.test(text)) {
|
|
65
|
+
this.signals.styleSignals.casual++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract domain keywords.
|
|
70
|
+
*/
|
|
71
|
+
extractDomainKeywords(text) {
|
|
72
|
+
const techWords = text.match(/\b[A-Z][a-zA-Z]{2,}(?:\.js|\.ts|\.py)?\b/g) ?? [];
|
|
73
|
+
techWords.forEach(w => this.signals.techKeywords.add(w));
|
|
74
|
+
// Chinese tech terms
|
|
75
|
+
const cnTerms = text.match(/(?:人工智能|机器学习|深度学习|大模型|微服务|架构|部署|运维|前端|后端|数据库|缓存|分布式)/g) ?? [];
|
|
76
|
+
cnTerms.forEach(w => this.signals.techKeywords.add(w));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Classify request type.
|
|
80
|
+
*/
|
|
81
|
+
classifyRequest(text) {
|
|
82
|
+
if (/\b(how to|怎么|如何|how do)\b/i.test(text))
|
|
83
|
+
return 'how-to';
|
|
84
|
+
if (/\b(why|为什么|原因)\b/i.test(text))
|
|
85
|
+
return 'explanation';
|
|
86
|
+
if (/\b(fix|error|bug|报错|出错|failed)\b/i.test(text))
|
|
87
|
+
return 'debugging';
|
|
88
|
+
if (/\b(review|评审|看看|check)\b/i.test(text))
|
|
89
|
+
return 'review';
|
|
90
|
+
if (/\b(create|build|write|写|创建|生成)\b/i.test(text))
|
|
91
|
+
return 'creation';
|
|
92
|
+
return 'general';
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Observe a user message and accumulate profile signals.
|
|
96
|
+
*/
|
|
97
|
+
async observe(message, brain) {
|
|
98
|
+
if (message.role !== 'user')
|
|
99
|
+
return;
|
|
100
|
+
const text = message.content;
|
|
101
|
+
// Language
|
|
102
|
+
const lang = this.detectLanguage(text);
|
|
103
|
+
this.signals.languages.set(lang, (this.signals.languages.get(lang) ?? 0) + 1);
|
|
104
|
+
// Style
|
|
105
|
+
this.detectStyle(text);
|
|
106
|
+
// Domain keywords
|
|
107
|
+
this.extractDomainKeywords(text);
|
|
108
|
+
// Request type
|
|
109
|
+
const reqType = this.classifyRequest(text);
|
|
110
|
+
this.signals.requestTypes.set(reqType, (this.signals.requestTypes.get(reqType) ?? 0) + 1);
|
|
111
|
+
this.observationCount++;
|
|
112
|
+
// Periodically persist to brain
|
|
113
|
+
if (brain?.learn && this.observationCount % this.LEARN_INTERVAL === 0) {
|
|
114
|
+
const profile = this.buildProfileFromSignals();
|
|
115
|
+
try {
|
|
116
|
+
await brain.learn(JSON.stringify(profile), { insight_type: 'user_profile' });
|
|
117
|
+
}
|
|
118
|
+
catch { /* non-critical */ }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
buildProfileFromSignals() {
|
|
122
|
+
// Language preference
|
|
123
|
+
let topLang = 'english';
|
|
124
|
+
let maxCount = 0;
|
|
125
|
+
for (const [lang, count] of this.signals.languages) {
|
|
126
|
+
if (count > maxCount) {
|
|
127
|
+
topLang = lang;
|
|
128
|
+
maxCount = count;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Communication style
|
|
132
|
+
const ss = this.signals.styleSignals;
|
|
133
|
+
const styles = [
|
|
134
|
+
['brief', ss.brief], ['detailed', ss.detailed],
|
|
135
|
+
['formal', ss.formal], ['casual', ss.casual],
|
|
136
|
+
];
|
|
137
|
+
const topStyle = styles.reduce((a, b) => (b[1] > a[1] ? b : a))[0];
|
|
138
|
+
// Top request types
|
|
139
|
+
const topRequests = [...this.signals.requestTypes.entries()]
|
|
140
|
+
.sort((a, b) => b[1] - a[1])
|
|
141
|
+
.slice(0, 5)
|
|
142
|
+
.map(([t]) => t);
|
|
143
|
+
return {
|
|
144
|
+
preferences: { language: topLang },
|
|
145
|
+
communication_style: topStyle,
|
|
146
|
+
expertise_areas: [...this.signals.techKeywords].slice(0, 20),
|
|
147
|
+
common_requests: topRequests,
|
|
148
|
+
last_updated: Date.now(),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get user profile, optionally from brain recall.
|
|
153
|
+
*/
|
|
154
|
+
async getProfile(brain) {
|
|
155
|
+
// First try local signals
|
|
156
|
+
if (this.observationCount > 0) {
|
|
157
|
+
return this.buildProfileFromSignals();
|
|
158
|
+
}
|
|
159
|
+
// Fallback to brain recall
|
|
160
|
+
if (brain?.recall) {
|
|
161
|
+
try {
|
|
162
|
+
const results = await brain.recall('user profile preferences style');
|
|
163
|
+
if (Array.isArray(results) && results.length > 0) {
|
|
164
|
+
const raw = typeof results[0] === 'string' ? results[0] : results[0].content;
|
|
165
|
+
return { ...EMPTY_PROFILE, ...JSON.parse(raw) };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch { /* ignore parse errors */ }
|
|
169
|
+
}
|
|
170
|
+
return { ...EMPTY_PROFILE };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Enhance a system prompt with user profile context.
|
|
174
|
+
*/
|
|
175
|
+
enhance(systemPrompt, profile) {
|
|
176
|
+
const hints = [];
|
|
177
|
+
if (profile.preferences.language) {
|
|
178
|
+
const langMap = {
|
|
179
|
+
chinese: 'User prefers Chinese responses.',
|
|
180
|
+
english: 'User prefers English responses.',
|
|
181
|
+
mixed: 'User uses mixed Chinese/English. Match their style.',
|
|
182
|
+
};
|
|
183
|
+
if (langMap[profile.preferences.language])
|
|
184
|
+
hints.push(langMap[profile.preferences.language]);
|
|
185
|
+
}
|
|
186
|
+
if (profile.communication_style && profile.communication_style !== 'unknown') {
|
|
187
|
+
hints.push(`User communication style: ${profile.communication_style}.`);
|
|
188
|
+
}
|
|
189
|
+
if (profile.expertise_areas.length > 0) {
|
|
190
|
+
hints.push(`User expertise: ${profile.expertise_areas.slice(0, 10).join(', ')}.`);
|
|
191
|
+
}
|
|
192
|
+
if (profile.common_requests.length > 0) {
|
|
193
|
+
hints.push(`Common request types: ${profile.common_requests.join(', ')}.`);
|
|
194
|
+
}
|
|
195
|
+
if (hints.length === 0)
|
|
196
|
+
return systemPrompt;
|
|
197
|
+
return `${systemPrompt}\n\n[User Profile] ${hints.join(' ')}`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
exports.UserProfiler = UserProfiler;
|
|
201
|
+
//# sourceMappingURL=user-profiler.js.map
|
|
@@ -9,5 +9,5 @@ export interface LLMProvider {
|
|
|
9
9
|
chatStream(messages: Message[], systemPrompt?: string): AsyncIterable<string>;
|
|
10
10
|
}
|
|
11
11
|
export declare function createProvider(name?: string, model?: string, baseUrl?: string, apiKey?: string): LLMProvider;
|
|
12
|
-
export declare const SUPPORTED_PROVIDERS: readonly ["openai", "ollama", "deepseek", "qwen", "gemini", "dashscope", "zhipu", "moonshot"];
|
|
12
|
+
export declare const SUPPORTED_PROVIDERS: readonly ["openai", "ollama", "claude-cli", "deepseek", "qwen", "gemini", "dashscope", "zhipu", "moonshot"];
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/providers/index.js
CHANGED
|
@@ -323,8 +323,61 @@ function isGeminiNative() {
|
|
|
323
323
|
const key = getApiKey();
|
|
324
324
|
return key.startsWith('AQ.') || (baseUrl.includes('googleapis.com') && !baseUrl.includes('/openai'));
|
|
325
325
|
}
|
|
326
|
+
class ClaudeCLIProvider {
|
|
327
|
+
name = 'claude-cli';
|
|
328
|
+
model;
|
|
329
|
+
constructor(model) {
|
|
330
|
+
this.model = model || 'sonnet';
|
|
331
|
+
}
|
|
332
|
+
async chat(messages, systemPrompt, options) {
|
|
333
|
+
const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
334
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
|
|
335
|
+
const execFileAsync = promisify(execFile);
|
|
336
|
+
// Build the prompt from messages
|
|
337
|
+
const lastMessage = messages[messages.length - 1];
|
|
338
|
+
if (!lastMessage)
|
|
339
|
+
return '';
|
|
340
|
+
let prompt = lastMessage.content;
|
|
341
|
+
// Add tool prompt if tools provided
|
|
342
|
+
if (options?.tools && options.tools.length > 0) {
|
|
343
|
+
prompt += buildToolPrompt(options.tools);
|
|
344
|
+
}
|
|
345
|
+
const args = ['--print'];
|
|
346
|
+
if (systemPrompt) {
|
|
347
|
+
args.push('--system-prompt', systemPrompt);
|
|
348
|
+
}
|
|
349
|
+
if (this.model) {
|
|
350
|
+
args.push('--model', this.model);
|
|
351
|
+
}
|
|
352
|
+
args.push(prompt);
|
|
353
|
+
try {
|
|
354
|
+
const { stdout } = await execFileAsync('claude', args, {
|
|
355
|
+
timeout: 120_000,
|
|
356
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
357
|
+
env: { ...process.env },
|
|
358
|
+
});
|
|
359
|
+
return stdout.trim();
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
if (err.code === 'ENOENT') {
|
|
363
|
+
throw new Error('Claude CLI not found. Install it: npm install -g @anthropic-ai/claude-code\n' +
|
|
364
|
+
'Then authenticate: claude login');
|
|
365
|
+
}
|
|
366
|
+
throw new Error(`Claude CLI error: ${err.message}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async *chatStream(messages, systemPrompt) {
|
|
370
|
+
// Claude CLI --print doesn't support streaming well, so we do single-shot
|
|
371
|
+
const result = await this.chat(messages, systemPrompt);
|
|
372
|
+
yield result;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
326
375
|
function createProvider(name = 'openai', model, baseUrl, apiKey) {
|
|
327
376
|
const finalModel = model || process.env.OPC_LLM_MODEL || 'gpt-4o-mini';
|
|
377
|
+
// Claude CLI mode: use local claude command (Claude Max/Pro subscription)
|
|
378
|
+
if (name === 'claude-cli' || process.env.OPC_LLM_PROVIDER === 'claude-cli') {
|
|
379
|
+
return new ClaudeCLIProvider(finalModel !== 'gpt-4o-mini' ? finalModel : undefined);
|
|
380
|
+
}
|
|
328
381
|
if (name === 'ollama') {
|
|
329
382
|
const ollamaBase = baseUrl || process.env.OPC_LLM_BASE_URL || 'http://localhost:11434/v1';
|
|
330
383
|
const ollamaKey = apiKey || process.env.OPC_LLM_API_KEY || 'ollama';
|
|
@@ -344,5 +397,5 @@ function createProvider(name = 'openai', model, baseUrl, apiKey) {
|
|
|
344
397
|
}
|
|
345
398
|
return new OpenAICompatibleProvider(resolvedName, finalModel, baseUrl, apiKey);
|
|
346
399
|
}
|
|
347
|
-
exports.SUPPORTED_PROVIDERS = ['openai', 'ollama', 'deepseek', 'qwen', 'gemini', 'dashscope', 'zhipu', 'moonshot'];
|
|
400
|
+
exports.SUPPORTED_PROVIDERS = ['openai', 'ollama', 'claude-cli', 'deepseek', 'qwen', 'gemini', 'dashscope', 'zhipu', 'moonshot'];
|
|
348
401
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron Engine — persistent scheduler with file-based storage.
|
|
3
|
+
* Manages scheduled tasks with cron expressions, persists to ~/.opc/schedules.json,
|
|
4
|
+
* and auto-recovers on startup.
|
|
5
|
+
*/
|
|
6
|
+
import type { JobHandler } from '../core/scheduler';
|
|
7
|
+
export interface ScheduleTask {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
schedule: string;
|
|
11
|
+
description: string;
|
|
12
|
+
frequency: 'daily' | 'weekly' | 'monthly' | 'custom';
|
|
13
|
+
time?: string;
|
|
14
|
+
outputChannel: 'telegram' | 'email' | 'web';
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
lastRun?: string;
|
|
19
|
+
nextRun?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SchedulesStore {
|
|
22
|
+
tasks: ScheduleTask[];
|
|
23
|
+
}
|
|
24
|
+
/** Convert frequency + time to cron expression */
|
|
25
|
+
export declare function frequencyToCron(frequency: string, time?: string): string;
|
|
26
|
+
export declare class CronEngine {
|
|
27
|
+
private scheduler;
|
|
28
|
+
private store;
|
|
29
|
+
private handler;
|
|
30
|
+
constructor(handler?: JobHandler);
|
|
31
|
+
/** Initialize and recover persisted tasks */
|
|
32
|
+
start(): void;
|
|
33
|
+
stop(): void;
|
|
34
|
+
listTasks(): ScheduleTask[];
|
|
35
|
+
getTask(id: string): ScheduleTask | undefined;
|
|
36
|
+
createTask(input: Omit<ScheduleTask, 'id' | 'createdAt' | 'updatedAt' | 'lastRun' | 'nextRun'>): ScheduleTask;
|
|
37
|
+
updateTask(id: string, updates: Partial<ScheduleTask>): ScheduleTask | null;
|
|
38
|
+
deleteTask(id: string): boolean;
|
|
39
|
+
runTask(id: string): Promise<boolean>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=cron-engine.d.ts.map
|