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.
Files changed (216) hide show
  1. package/README.md +404 -74
  2. package/README.zh-CN.md +82 -0
  3. package/dist/channels/dingtalk.d.ts +17 -0
  4. package/dist/channels/dingtalk.js +38 -0
  5. package/dist/channels/googlechat.d.ts +14 -0
  6. package/dist/channels/googlechat.js +37 -0
  7. package/dist/channels/imessage.d.ts +13 -0
  8. package/dist/channels/imessage.js +28 -0
  9. package/dist/channels/irc.d.ts +20 -0
  10. package/dist/channels/irc.js +71 -0
  11. package/dist/channels/line.d.ts +14 -0
  12. package/dist/channels/line.js +28 -0
  13. package/dist/channels/matrix.d.ts +15 -0
  14. package/dist/channels/matrix.js +28 -0
  15. package/dist/channels/mattermost.d.ts +18 -0
  16. package/dist/channels/mattermost.js +49 -0
  17. package/dist/channels/msteams.d.ts +14 -0
  18. package/dist/channels/msteams.js +28 -0
  19. package/dist/channels/nostr.d.ts +14 -0
  20. package/dist/channels/nostr.js +28 -0
  21. package/dist/channels/qq.d.ts +15 -0
  22. package/dist/channels/qq.js +28 -0
  23. package/dist/channels/signal.d.ts +14 -0
  24. package/dist/channels/signal.js +28 -0
  25. package/dist/channels/sms.d.ts +15 -0
  26. package/dist/channels/sms.js +28 -0
  27. package/dist/channels/twitch.d.ts +17 -0
  28. package/dist/channels/twitch.js +59 -0
  29. package/dist/channels/voice-call.d.ts +27 -0
  30. package/dist/channels/voice-call.js +82 -0
  31. package/dist/channels/whatsapp.d.ts +14 -0
  32. package/dist/channels/whatsapp.js +28 -0
  33. package/dist/cli/chat.d.ts +2 -0
  34. package/dist/cli/chat.js +134 -0
  35. package/dist/cli/setup.d.ts +4 -0
  36. package/dist/cli/setup.js +303 -0
  37. package/dist/cli.js +142 -6
  38. package/dist/core/api-server.d.ts +25 -0
  39. package/dist/core/api-server.js +286 -0
  40. package/dist/core/audio.d.ts +50 -0
  41. package/dist/core/audio.js +68 -0
  42. package/dist/core/context-discovery.d.ts +16 -0
  43. package/dist/core/context-discovery.js +107 -0
  44. package/dist/core/context-refs.d.ts +29 -0
  45. package/dist/core/context-refs.js +162 -0
  46. package/dist/core/gateway.d.ts +53 -0
  47. package/dist/core/gateway.js +80 -0
  48. package/dist/core/heartbeat.d.ts +19 -0
  49. package/dist/core/heartbeat.js +50 -0
  50. package/dist/core/hooks.d.ts +28 -0
  51. package/dist/core/hooks.js +82 -0
  52. package/dist/core/ide-bridge.d.ts +53 -0
  53. package/dist/core/ide-bridge.js +97 -0
  54. package/dist/core/node-network.d.ts +23 -0
  55. package/dist/core/node-network.js +77 -0
  56. package/dist/core/profiles.d.ts +27 -0
  57. package/dist/core/profiles.js +131 -0
  58. package/dist/core/sandbox.d.ts +25 -0
  59. package/dist/core/sandbox.js +84 -1
  60. package/dist/core/session-manager.d.ts +33 -0
  61. package/dist/core/session-manager.js +157 -0
  62. package/dist/core/vision.d.ts +45 -0
  63. package/dist/core/vision.js +177 -0
  64. package/dist/hub/brain-seed.d.ts +14 -0
  65. package/dist/hub/brain-seed.js +77 -0
  66. package/dist/hub/client.d.ts +25 -0
  67. package/dist/hub/client.js +44 -0
  68. package/dist/index.d.ts +66 -1
  69. package/dist/index.js +95 -3
  70. package/dist/memory/context-compressor.d.ts +43 -0
  71. package/dist/memory/context-compressor.js +167 -0
  72. package/dist/memory/index.d.ts +4 -0
  73. package/dist/memory/index.js +5 -1
  74. package/dist/memory/user-profiler.d.ts +50 -0
  75. package/dist/memory/user-profiler.js +201 -0
  76. package/dist/providers/index.d.ts +1 -1
  77. package/dist/providers/index.js +54 -1
  78. package/dist/scheduler/cron-engine.d.ts +41 -0
  79. package/dist/scheduler/cron-engine.js +200 -0
  80. package/dist/scheduler/index.d.ts +3 -0
  81. package/dist/scheduler/index.js +7 -0
  82. package/dist/schema/oad.d.ts +12 -12
  83. package/dist/security/approvals.d.ts +53 -0
  84. package/dist/security/approvals.js +115 -0
  85. package/dist/security/elevated.d.ts +41 -0
  86. package/dist/security/elevated.js +89 -0
  87. package/dist/security/index.d.ts +6 -0
  88. package/dist/security/index.js +7 -1
  89. package/dist/security/secrets.d.ts +34 -0
  90. package/dist/security/secrets.js +115 -0
  91. package/dist/skills/builtin/index.d.ts +6 -0
  92. package/dist/skills/builtin/index.js +402 -0
  93. package/dist/skills/marketplace.d.ts +30 -0
  94. package/dist/skills/marketplace.js +142 -0
  95. package/dist/skills/types.d.ts +34 -0
  96. package/dist/skills/types.js +16 -0
  97. package/dist/studio/server.d.ts +25 -0
  98. package/dist/studio/server.js +780 -0
  99. package/dist/studio/templates-data.d.ts +21 -0
  100. package/dist/studio/templates-data.js +148 -0
  101. package/dist/studio-ui/index.html +2502 -1073
  102. package/dist/tools/builtin/browser.d.ts +47 -0
  103. package/dist/tools/builtin/browser.js +284 -0
  104. package/dist/tools/builtin/home-assistant.d.ts +12 -0
  105. package/dist/tools/builtin/home-assistant.js +126 -0
  106. package/dist/tools/builtin/index.d.ts +7 -1
  107. package/dist/tools/builtin/index.js +23 -2
  108. package/dist/tools/builtin/rl-tools.d.ts +13 -0
  109. package/dist/tools/builtin/rl-tools.js +228 -0
  110. package/dist/tools/builtin/vision.d.ts +6 -0
  111. package/dist/tools/builtin/vision.js +61 -0
  112. package/dist/tools/builtin/web-search.d.ts +9 -0
  113. package/dist/tools/builtin/web-search.js +150 -0
  114. package/dist/tools/document-processor.d.ts +39 -0
  115. package/dist/tools/document-processor.js +188 -0
  116. package/dist/tools/image-generator.d.ts +42 -0
  117. package/dist/tools/image-generator.js +136 -0
  118. package/dist/tools/web-scraper.d.ts +20 -0
  119. package/dist/tools/web-scraper.js +148 -0
  120. package/dist/tools/web-search.d.ts +51 -0
  121. package/dist/tools/web-search.js +152 -0
  122. package/install.ps1 +154 -0
  123. package/install.sh +164 -0
  124. package/package.json +63 -52
  125. package/src/channels/dingtalk.ts +46 -0
  126. package/src/channels/googlechat.ts +42 -0
  127. package/src/channels/imessage.ts +32 -0
  128. package/src/channels/irc.ts +82 -0
  129. package/src/channels/line.ts +33 -0
  130. package/src/channels/matrix.ts +34 -0
  131. package/src/channels/mattermost.ts +57 -0
  132. package/src/channels/msteams.ts +33 -0
  133. package/src/channels/nostr.ts +33 -0
  134. package/src/channels/qq.ts +34 -0
  135. package/src/channels/signal.ts +33 -0
  136. package/src/channels/sms.ts +34 -0
  137. package/src/channels/twitch.ts +65 -0
  138. package/src/channels/voice-call.ts +100 -0
  139. package/src/channels/whatsapp.ts +33 -0
  140. package/src/cli/chat.ts +99 -0
  141. package/src/cli/setup.ts +314 -0
  142. package/src/cli.ts +148 -6
  143. package/src/core/api-server.ts +277 -0
  144. package/src/core/audio.ts +98 -0
  145. package/src/core/context-discovery.ts +85 -0
  146. package/src/core/context-refs.ts +140 -0
  147. package/src/core/gateway.ts +106 -0
  148. package/src/core/heartbeat.ts +51 -0
  149. package/src/core/hooks.ts +105 -0
  150. package/src/core/ide-bridge.ts +133 -0
  151. package/src/core/node-network.ts +86 -0
  152. package/src/core/profiles.ts +122 -0
  153. package/src/core/sandbox.ts +100 -0
  154. package/src/core/session-manager.ts +137 -0
  155. package/src/core/vision.ts +180 -0
  156. package/src/hub/brain-seed.ts +54 -0
  157. package/src/hub/client.ts +60 -0
  158. package/src/index.ts +86 -1
  159. package/src/memory/context-compressor.ts +189 -0
  160. package/src/memory/index.ts +4 -0
  161. package/src/memory/user-profiler.ts +215 -0
  162. package/src/providers/index.ts +64 -1
  163. package/src/scheduler/cron-engine.ts +191 -0
  164. package/src/scheduler/index.ts +2 -0
  165. package/src/security/approvals.ts +143 -0
  166. package/src/security/elevated.ts +105 -0
  167. package/src/security/index.ts +6 -0
  168. package/src/security/secrets.ts +129 -0
  169. package/src/skills/builtin/index.ts +408 -0
  170. package/src/skills/marketplace.ts +113 -0
  171. package/src/skills/types.ts +42 -0
  172. package/src/studio/server.ts +1591 -791
  173. package/src/studio/templates-data.ts +178 -0
  174. package/src/studio-ui/index.html +2502 -1073
  175. package/src/tools/builtin/browser.ts +299 -0
  176. package/src/tools/builtin/home-assistant.ts +116 -0
  177. package/src/tools/builtin/index.ts +37 -28
  178. package/src/tools/builtin/rl-tools.ts +243 -0
  179. package/src/tools/builtin/vision.ts +64 -0
  180. package/src/tools/builtin/web-search.ts +126 -0
  181. package/src/tools/document-processor.ts +213 -0
  182. package/src/tools/image-generator.ts +150 -0
  183. package/src/tools/web-scraper.ts +179 -0
  184. package/src/tools/web-search.ts +180 -0
  185. package/tests/api-server.test.ts +148 -0
  186. package/tests/approvals.test.ts +89 -0
  187. package/tests/audio.test.ts +40 -0
  188. package/tests/browser.test.ts +179 -0
  189. package/tests/builtin-tools.test.ts +83 -83
  190. package/tests/channels-extra.test.ts +45 -0
  191. package/tests/context-compressor.test.ts +172 -0
  192. package/tests/context-refs.test.ts +121 -0
  193. package/tests/cron-engine.test.ts +101 -0
  194. package/tests/document-processor.test.ts +69 -0
  195. package/tests/e2e-nocode.test.ts +442 -0
  196. package/tests/elevated.test.ts +69 -0
  197. package/tests/gateway.test.ts +63 -71
  198. package/tests/home-assistant.test.ts +40 -0
  199. package/tests/hooks.test.ts +79 -0
  200. package/tests/ide-bridge.test.ts +38 -0
  201. package/tests/image-generator.test.ts +84 -0
  202. package/tests/node-network.test.ts +74 -0
  203. package/tests/profiles.test.ts +61 -0
  204. package/tests/rl-tools.test.ts +93 -0
  205. package/tests/sandbox-manager.test.ts +46 -0
  206. package/tests/secrets.test.ts +107 -0
  207. package/tests/settings-api.test.ts +148 -0
  208. package/tests/setup.test.ts +73 -0
  209. package/tests/studio.test.ts +402 -229
  210. package/tests/tools/builtin-extended.test.ts +138 -138
  211. package/tests/user-profiler.test.ts +169 -0
  212. package/tests/v090-features.test.ts +254 -0
  213. package/tests/vision.test.ts +61 -0
  214. package/tests/voice-call.test.ts +47 -0
  215. package/tests/voice-interaction.test.ts +38 -0
  216. 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
@@ -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;
@@ -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
@@ -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