aegiscode 3.1.8 → 3.1.10
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 +23 -15
- package/dist/main.js +549 -552
- package/package.json +4 -2
- package/src/agent/Agent.ts +903 -0
- package/src/agent/SimpleAgent.ts +48 -0
- package/src/agent/index.ts +54 -0
- package/src/agent/orchestrator/AppBuilder.ts +443 -0
- package/src/agent/orchestrator/CouncilAgent.ts +310 -0
- package/src/agent/orchestrator/DiscussionRoom.ts +462 -0
- package/src/agent/orchestrator/OrchestratorAgent.ts +637 -0
- package/src/agent/orchestrator/index.ts +38 -0
- package/src/agent/orchestrator/utils.ts +397 -0
- package/src/agent/pricing.ts +115 -0
- package/src/agent/router.ts +74 -0
- package/src/agent/routerStats.ts +121 -0
- package/src/agent/types.ts +318 -0
- package/src/auth/login.ts +383 -0
- package/src/cli/config.ts +189 -0
- package/src/cli/index.ts +17 -0
- package/src/cli/middleware.ts +119 -0
- package/src/cli/types.ts +75 -0
- package/src/config/ConfigManager.ts +587 -0
- package/src/config/index.ts +7 -0
- package/src/config/types.ts +584 -0
- package/src/context/CompactionService.ts +300 -0
- package/src/context/ContextManager.ts +450 -0
- package/src/context/FileAnalyzer.ts +267 -0
- package/src/context/TokenCounter.ts +265 -0
- package/src/context/index.ts +27 -0
- package/src/context/storage/CacheStore.ts +176 -0
- package/src/context/storage/JSONLStore.ts +201 -0
- package/src/context/storage/MemoryStore.ts +205 -0
- package/src/context/storage/PersistentStore.ts +327 -0
- package/src/context/storage/index.ts +9 -0
- package/src/context/storage/pathUtils.ts +114 -0
- package/src/context/test.ts +309 -0
- package/src/context/types.ts +268 -0
- package/src/hooks/HookExecutor.ts +434 -0
- package/src/hooks/HookManager.ts +596 -0
- package/src/hooks/HookService.ts +269 -0
- package/src/hooks/Matcher.ts +157 -0
- package/src/hooks/index.ts +63 -0
- package/src/hooks/types.ts +424 -0
- package/src/main.tsx +596 -0
- package/src/mcp/HealthMonitor.ts +150 -0
- package/src/mcp/McpClient.ts +491 -0
- package/src/mcp/McpRegistry.ts +321 -0
- package/src/mcp/createMcpTool.ts +251 -0
- package/src/mcp/index.ts +15 -0
- package/src/mcp/server.ts +334 -0
- package/src/mcp/test-server.ts +88 -0
- package/src/mcp/test.ts +372 -0
- package/src/mcp/types.ts +247 -0
- package/src/memory/AgentMemoryBus.ts +432 -0
- package/src/memory/CloudSync.ts +99 -0
- package/src/memory/DriveSync.ts +106 -0
- package/src/memory/SharedMemory.ts +951 -0
- package/src/memory/index.ts +14 -0
- package/src/memory/machineFingerprint.ts +40 -0
- package/src/orchestrator/SubAgentMetadata.ts +136 -0
- package/src/prompts/builder.ts +213 -0
- package/src/prompts/default.ts +144 -0
- package/src/prompts/index.ts +16 -0
- package/src/prompts/plan.ts +64 -0
- package/src/prompts/test.ts +78 -0
- package/src/services/AnthropicChatService.ts +341 -0
- package/src/services/ChatService.ts +347 -0
- package/src/services/ClaudeCliChatService.ts +256 -0
- package/src/services/CloudSync.ts +168 -0
- package/src/services/CostLedger.ts +211 -0
- package/src/services/Heartbeat.ts +135 -0
- package/src/services/LearningCollector.ts +291 -0
- package/src/services/OllamaInstaller.ts +342 -0
- package/src/services/VersionChecker.ts +445 -0
- package/src/services/index.ts +57 -0
- package/src/services/streaming/OpenAIEventAdapter.ts +126 -0
- package/src/services/streaming/RenderingProfile.ts +90 -0
- package/src/services/streaming/StreamEventParser.ts +181 -0
- package/src/services/streaming/ThrottledRenderer.ts +139 -0
- package/src/services/streaming/TranscriptBuffer.ts +574 -0
- package/src/services/streaming/eventStatusMap.ts +52 -0
- package/src/services/streaming/index.ts +46 -0
- package/src/services/streaming/renderFormatting.ts +79 -0
- package/src/services/streaming/types.ts +234 -0
- package/src/skills/SkillLoader.ts +126 -0
- package/src/skills/SkillRegistry.ts +366 -0
- package/src/skills/index.ts +48 -0
- package/src/skills/types.ts +146 -0
- package/src/slash-commands/billing.ts +70 -0
- package/src/slash-commands/build.ts +413 -0
- package/src/slash-commands/builtinCommands.ts +2733 -0
- package/src/slash-commands/clone.ts +242 -0
- package/src/slash-commands/council.ts +125 -0
- package/src/slash-commands/custom/CustomCommandExecutor.ts +143 -0
- package/src/slash-commands/custom/CustomCommandLoader.ts +251 -0
- package/src/slash-commands/custom/CustomCommandRegistry.ts +144 -0
- package/src/slash-commands/custom/index.ts +7 -0
- package/src/slash-commands/debate.ts +254 -0
- package/src/slash-commands/gmail.ts +105 -0
- package/src/slash-commands/index.ts +388 -0
- package/src/slash-commands/mcpCommand.ts +205 -0
- package/src/slash-commands/types.ts +201 -0
- package/src/store/index.ts +76 -0
- package/src/store/selectors.ts +246 -0
- package/src/store/slices/appSlice.ts +205 -0
- package/src/store/slices/commandSlice.ts +115 -0
- package/src/store/slices/configSlice.ts +46 -0
- package/src/store/slices/focusSlice.ts +64 -0
- package/src/store/slices/index.ts +9 -0
- package/src/store/slices/sessionSlice.ts +424 -0
- package/src/store/streaming-buffer.ts +425 -0
- package/src/store/test.ts +296 -0
- package/src/store/types.ts +274 -0
- package/src/store/vanilla.ts +186 -0
- package/src/tools/builtin/bash.ts +236 -0
- package/src/tools/builtin/council.ts +105 -0
- package/src/tools/builtin/edit.ts +213 -0
- package/src/tools/builtin/glob.ts +136 -0
- package/src/tools/builtin/grep.ts +263 -0
- package/src/tools/builtin/index.ts +61 -0
- package/src/tools/builtin/memory.ts +66 -0
- package/src/tools/builtin/read.ts +168 -0
- package/src/tools/builtin/skill.ts +97 -0
- package/src/tools/builtin/snapshot.ts +40 -0
- package/src/tools/builtin/task.ts +106 -0
- package/src/tools/builtin/write.ts +134 -0
- package/src/tools/createTool.ts +221 -0
- package/src/tools/execution/ExecutionPipeline.ts +263 -0
- package/src/tools/execution/index.ts +40 -0
- package/src/tools/execution/stages/CacheStage.ts +131 -0
- package/src/tools/execution/stages/ConfirmationStage.ts +203 -0
- package/src/tools/execution/stages/DiscoveryStage.ts +46 -0
- package/src/tools/execution/stages/ExecutionStage.ts +48 -0
- package/src/tools/execution/stages/FormattingStage.ts +44 -0
- package/src/tools/execution/stages/HookStage.ts +72 -0
- package/src/tools/execution/stages/PermissionStage.ts +287 -0
- package/src/tools/execution/stages/PostHookStage.ts +71 -0
- package/src/tools/execution/stages/index.ts +12 -0
- package/src/tools/execution/test.ts +266 -0
- package/src/tools/execution/types.ts +273 -0
- package/src/tools/index.ts +81 -0
- package/src/tools/registry.ts +304 -0
- package/src/tools/schemas.ts +109 -0
- package/src/tools/test.ts +220 -0
- package/src/tools/types.ts +175 -0
- package/src/tools/validation/PermissionChecker.ts +242 -0
- package/src/tools/validation/SensitiveFileDetector.ts +210 -0
- package/src/tools/validation/index.ts +11 -0
- package/src/ui/App.tsx +166 -0
- package/src/ui/components/AegisInterface.tsx +484 -0
- package/src/ui/components/common/ChatSearch.tsx +150 -0
- package/src/ui/components/common/ErrorBoundary.tsx +82 -0
- package/src/ui/components/common/ExitMessage.tsx +120 -0
- package/src/ui/components/common/LoadingIndicator.tsx +49 -0
- package/src/ui/components/common/index.ts +6 -0
- package/src/ui/components/dialog/ConfirmationPrompt.tsx +208 -0
- package/src/ui/components/dialog/InteractiveSelector.tsx +149 -0
- package/src/ui/components/dialog/SetupWizard.tsx +297 -0
- package/src/ui/components/dialog/UpdatePrompt.tsx +155 -0
- package/src/ui/components/dialog/index.ts +8 -0
- package/src/ui/components/index.ts +28 -0
- package/src/ui/components/input/CommandSuggestions.tsx +139 -0
- package/src/ui/components/input/CustomTextInput.tsx +220 -0
- package/src/ui/components/input/InputArea.tsx +361 -0
- package/src/ui/components/input/PromptSuggestions.tsx +66 -0
- package/src/ui/components/input/index.ts +6 -0
- package/src/ui/components/layout/ChatStatusBar.tsx +90 -0
- package/src/ui/components/layout/ContextBar.tsx +79 -0
- package/src/ui/components/layout/MessageArea.tsx +96 -0
- package/src/ui/components/layout/MessageList.tsx +647 -0
- package/src/ui/components/layout/MessageSeparator.tsx +26 -0
- package/src/ui/components/layout/WelcomeMessage.tsx +93 -0
- package/src/ui/components/layout/index.ts +7 -0
- package/src/ui/components/markdown/CodeHighlighter.tsx +292 -0
- package/src/ui/components/markdown/MessageRenderer.tsx +1211 -0
- package/src/ui/components/markdown/index.ts +8 -0
- package/src/ui/components/markdown/parser.ts +336 -0
- package/src/ui/components/markdown/types.ts +66 -0
- package/src/ui/focus/FocusManager.ts +137 -0
- package/src/ui/focus/index.ts +13 -0
- package/src/ui/focus/types.ts +54 -0
- package/src/ui/focus/useFocus.ts +75 -0
- package/src/ui/hooks/index.ts +11 -0
- package/src/ui/hooks/useAgent.ts +284 -0
- package/src/ui/hooks/useCommandHistory.ts +87 -0
- package/src/ui/hooks/useCommandProcessor.ts +443 -0
- package/src/ui/hooks/useConfirmation.ts +99 -0
- package/src/ui/hooks/useCtrlCHandler.ts +100 -0
- package/src/ui/hooks/useInputBuffer.ts +122 -0
- package/src/ui/hooks/useTerminalSize.ts +68 -0
- package/src/ui/hooks/useTerminalWidth.ts +5 -0
- package/src/ui/hooks/useWindowedList.ts +118 -0
- package/src/ui/render-debugger.ts +621 -0
- package/src/ui/test.ts +189 -0
- package/src/ui/themes/ThemeManager.ts +332 -0
- package/src/ui/themes/aegisTheme.ts +87 -0
- package/src/ui/themes/darkTheme.ts +87 -0
- package/src/ui/themes/defaultTheme.ts +85 -0
- package/src/ui/themes/index.ts +10 -0
- package/src/ui/themes/lightTheme.ts +89 -0
- package/src/ui/themes/popularThemes.ts +187 -0
- package/src/ui/themes/types.ts +130 -0
- package/src/utils/clipboard.ts +48 -0
- package/src/utils/debug.ts +43 -0
- package/src/utils/environment.ts +68 -0
- package/src/utils/index.ts +10 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VersionChecker - 版本检查服务
|
|
3
|
+
*
|
|
4
|
+
*
|
|
5
|
+
* - 启动时并行检查是否有新版本
|
|
6
|
+
* - 缓存机制(1小时 TTL)
|
|
7
|
+
* - 跳过版本功能(Skip until next version)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import fsPromises from 'fs/promises';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import { spawn } from 'child_process';
|
|
16
|
+
|
|
17
|
+
// ========== 从 package.json 读取配
|
|
18
|
+
|
|
19
|
+
interface PackageJson {
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
repository?: {
|
|
23
|
+
type?: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
} | string;
|
|
26
|
+
homepage?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
*
|
|
32
|
+
*
|
|
33
|
+
*
|
|
34
|
+
*
|
|
35
|
+
*/
|
|
36
|
+
function readPackageJsonSync(): PackageJson {
|
|
37
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
38
|
+
const __dirname = path.dirname(__filename);
|
|
39
|
+
|
|
40
|
+
// 可能的 package.json 路径(基
|
|
41
|
+
const possiblePaths = [
|
|
42
|
+
path.resolve(__dirname, '../package.json'), // 打包
|
|
43
|
+
path.resolve(__dirname, '../../package.json'), // 开发环
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
for (const pkgPath of possiblePaths) {
|
|
47
|
+
try {
|
|
48
|
+
const content = fs.readFileSync(pkgPath, 'utf-8');
|
|
49
|
+
const pkg = JSON.parse(content) as PackageJson;
|
|
50
|
+
// 验证是否是正确的 package(名字匹
|
|
51
|
+
if (pkg.name === 'aegis' || pkg.name === 'aegis-cli' || pkg.name === 'aegiscode') {
|
|
52
|
+
return pkg;
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// 继续尝试下一个路
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 如果都失败,使用默认
|
|
60
|
+
return { name: 'aegis', version: '0.1.0' };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const packageJson = readPackageJsonSync();
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
*
|
|
67
|
+
*
|
|
68
|
+
*/
|
|
69
|
+
function getReleaseNotesUrl(): string {
|
|
70
|
+
// 从 repository.url 提
|
|
71
|
+
const repoUrl = typeof packageJson.repository === 'string'
|
|
72
|
+
? packageJson.repository
|
|
73
|
+
: packageJson.repository?.url;
|
|
74
|
+
|
|
75
|
+
if (repoUrl) {
|
|
76
|
+
// 转
|
|
77
|
+
const match = repoUrl.match(/github\.com[/:]([^/]+\/[^/.]+)/);
|
|
78
|
+
if (match) {
|
|
79
|
+
return `https://github.com/${match[1]}/releases`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 从 homepage 提
|
|
84
|
+
if (packageJson.homepage) {
|
|
85
|
+
const homepageMatch = packageJson.homepage.match(/github\.com\/([^/#]+\/[^/#]+)/);
|
|
86
|
+
if (homepageMatch) {
|
|
87
|
+
return `https://github.com/${homepageMatch[1]}/releases`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 默认
|
|
92
|
+
return `https://www.npmjs.com/package/${PACKAGE_NAME}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ========== 配置常
|
|
96
|
+
|
|
97
|
+
const PACKAGE_NAME = packageJson.name;
|
|
98
|
+
const CURRENT_VERSION = packageJson.version;
|
|
99
|
+
const DEFAULT_NPM_REGISTRY_URL = 'https://registry.npmjs.org';
|
|
100
|
+
const CACHE_TTL = 60 * 60 * 1000; // 1 小
|
|
101
|
+
const CACHE_DIR = path.join(os.homedir(), `.${PACKAGE_NAME}`);
|
|
102
|
+
const CACHE_FILE = path.join(CACHE_DIR, 'version-cache.json');
|
|
103
|
+
|
|
104
|
+
// ========== 读取用户 npm 配
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
*
|
|
108
|
+
*
|
|
109
|
+
*/
|
|
110
|
+
async function getNpmRegistry(): Promise<string> {
|
|
111
|
+
const npmrcLocations = [
|
|
112
|
+
path.join(process.cwd(), '.npmrc'), // 项目
|
|
113
|
+
path.join(os.homedir(), '.npmrc'), // 用户
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
for (const npmrcPath of npmrcLocations) {
|
|
117
|
+
try {
|
|
118
|
+
const content = await fsPromises.readFile(npmrcPath, 'utf-8');
|
|
119
|
+
// 匹
|
|
120
|
+
const match = content.match(/^\s*registry\s*=\s*(.+?)\s*$/m);
|
|
121
|
+
if (match && match[1]) {
|
|
122
|
+
let registry = match[1].trim();
|
|
123
|
+
// 移除尾部斜
|
|
124
|
+
if (registry.endsWith('/')) {
|
|
125
|
+
registry = registry.slice(0, -1);
|
|
126
|
+
}
|
|
127
|
+
return registry;
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
// 文件不存在或读取失败,继续尝试下一
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return DEFAULT_NPM_REGISTRY_URL;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ========== 类型定
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
*
|
|
141
|
+
*/
|
|
142
|
+
export interface VersionCheckResult {
|
|
143
|
+
currentVersion: string;
|
|
144
|
+
latestVersion: string | null;
|
|
145
|
+
hasUpdate: boolean;
|
|
146
|
+
shouldPrompt: boolean; // 是否应该显示提示(考虑 skip 设
|
|
147
|
+
releaseNotesUrl: string;
|
|
148
|
+
error?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
*
|
|
153
|
+
*/
|
|
154
|
+
interface VersionCache {
|
|
155
|
+
latestVersion: string;
|
|
156
|
+
checkedAt: number;
|
|
157
|
+
skipUntilVersion?: string; // 跳过直到此版
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ========== 缓存操
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
*
|
|
164
|
+
*/
|
|
165
|
+
async function readCache(): Promise<VersionCache | null> {
|
|
166
|
+
try {
|
|
167
|
+
const content = await fsPromises.readFile(CACHE_FILE, 'utf-8');
|
|
168
|
+
const cache: VersionCache = JSON.parse(content);
|
|
169
|
+
return cache;
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
*
|
|
177
|
+
*/
|
|
178
|
+
async function writeCache(cache: VersionCache): Promise<void> {
|
|
179
|
+
try {
|
|
180
|
+
await fsPromises.mkdir(CACHE_DIR, { recursive: true });
|
|
181
|
+
await fsPromises.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
182
|
+
} catch (error) {
|
|
183
|
+
// 缓存写入失败不影响主流
|
|
184
|
+
console.error('[VersionChecker] Failed to write cache:', error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
*
|
|
190
|
+
*/
|
|
191
|
+
function isCacheValid(cache: VersionCache): boolean {
|
|
192
|
+
return Date.now() - cache.checkedAt < CACHE_TTL;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ========== 版本比
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
*
|
|
199
|
+
*
|
|
200
|
+
*/
|
|
201
|
+
function compareVersions(a: string, b: string): number {
|
|
202
|
+
const partsA = a.replace(/^v/, '').split('.').map(Number);
|
|
203
|
+
const partsB = b.replace(/^v/, '').split('.').map(Number);
|
|
204
|
+
|
|
205
|
+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
206
|
+
const numA = partsA[i] || 0;
|
|
207
|
+
const numB = partsB[i] || 0;
|
|
208
|
+
if (numA > numB) return 1;
|
|
209
|
+
if (numA < numB) return -1;
|
|
210
|
+
}
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ========== 核心功
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
*
|
|
218
|
+
*/
|
|
219
|
+
async function fetchLatestVersion(): Promise<string | null> {
|
|
220
|
+
try {
|
|
221
|
+
const controller = new AbortController();
|
|
222
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5秒超
|
|
223
|
+
|
|
224
|
+
// 从用户配置读
|
|
225
|
+
const registryUrl = await getNpmRegistry();
|
|
226
|
+
|
|
227
|
+
const response = await fetch(`${registryUrl}/${PACKAGE_NAME}/latest`, {
|
|
228
|
+
signal: controller.signal,
|
|
229
|
+
headers: {
|
|
230
|
+
'Accept': 'application/json',
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
clearTimeout(timeoutId);
|
|
235
|
+
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const data = await response.json() as { version?: string };
|
|
241
|
+
return data.version || null;
|
|
242
|
+
} catch {
|
|
243
|
+
// 网络错误、超时等,静默失
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
*
|
|
250
|
+
*
|
|
251
|
+
* @param forceCheck - 是否强制检查(忽略缓存)
|
|
252
|
+
*/
|
|
253
|
+
export async function checkVersion(forceCheck = false): Promise<VersionCheckResult> {
|
|
254
|
+
const result: VersionCheckResult = {
|
|
255
|
+
currentVersion: CURRENT_VERSION,
|
|
256
|
+
latestVersion: null,
|
|
257
|
+
hasUpdate: false,
|
|
258
|
+
shouldPrompt: false,
|
|
259
|
+
releaseNotesUrl: getReleaseNotesUrl(),
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
// 1. 尝试读取缓
|
|
264
|
+
const cache = await readCache();
|
|
265
|
+
|
|
266
|
+
// 2. 如果缓存有效且不强制检查,使用缓
|
|
267
|
+
if (cache && isCacheValid(cache) && !forceCheck) {
|
|
268
|
+
result.latestVersion = cache.latestVersion;
|
|
269
|
+
} else {
|
|
270
|
+
// 3. 从 npm 获取最新版
|
|
271
|
+
const latestVersion = await fetchLatestVersion();
|
|
272
|
+
|
|
273
|
+
if (latestVersion) {
|
|
274
|
+
result.latestVersion = latestVersion;
|
|
275
|
+
|
|
276
|
+
// 4. 更新缓存(保
|
|
277
|
+
await writeCache({
|
|
278
|
+
latestVersion,
|
|
279
|
+
checkedAt: Date.now(),
|
|
280
|
+
skipUntilVersion: cache?.skipUntilVersion,
|
|
281
|
+
});
|
|
282
|
+
} else if (cache) {
|
|
283
|
+
// 获取失败但有旧缓存,使用旧数
|
|
284
|
+
result.latestVersion = cache.latestVersion;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 5. 判断是否有更
|
|
289
|
+
if (result.latestVersion) {
|
|
290
|
+
result.hasUpdate = compareVersions(result.latestVersion, CURRENT_VERSION) > 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 6. 判断是否应该提示(考虑 skip 设
|
|
294
|
+
if (result.hasUpdate && result.latestVersion) {
|
|
295
|
+
const skipVersion = cache?.skipUntilVersion;
|
|
296
|
+
if (skipVersion) {
|
|
297
|
+
// 如果最新版本大于跳过版本,则应该提
|
|
298
|
+
result.shouldPrompt = compareVersions(result.latestVersion, skipVersion) > 0;
|
|
299
|
+
} else {
|
|
300
|
+
result.shouldPrompt = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
} catch (error) {
|
|
305
|
+
result.error = error instanceof Error ? error.message : 'Unknown error';
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
*
|
|
313
|
+
*
|
|
314
|
+
*
|
|
315
|
+
*/
|
|
316
|
+
export async function checkVersionOnStartup(): Promise<VersionCheckResult | null> {
|
|
317
|
+
const result = await checkVersion();
|
|
318
|
+
|
|
319
|
+
// 只有需要提示时才返回结
|
|
320
|
+
if (result.shouldPrompt) {
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
*
|
|
329
|
+
*/
|
|
330
|
+
export async function setSkipUntilVersion(version: string): Promise<void> {
|
|
331
|
+
const cache = await readCache();
|
|
332
|
+
await writeCache({
|
|
333
|
+
latestVersion: cache?.latestVersion || version,
|
|
334
|
+
checkedAt: cache?.checkedAt || Date.now(),
|
|
335
|
+
skipUntilVersion: version,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
*
|
|
341
|
+
*/
|
|
342
|
+
export async function clearSkipVersion(): Promise<void> {
|
|
343
|
+
const cache = await readCache();
|
|
344
|
+
if (cache) {
|
|
345
|
+
await writeCache({
|
|
346
|
+
latestVersion: cache.latestVersion,
|
|
347
|
+
checkedAt: cache.checkedAt,
|
|
348
|
+
// 不设
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
*
|
|
355
|
+
*
|
|
356
|
+
*/
|
|
357
|
+
const NPM_GLOBAL_PREFIX = path.join(os.homedir(), '.npm-global');
|
|
358
|
+
const NPM_GLOBAL_BIN = path.join(NPM_GLOBAL_PREFIX, 'bin');
|
|
359
|
+
|
|
360
|
+
function isPrefixBinInPath(): boolean {
|
|
361
|
+
const pathEntries = (process.env.PATH || '').split(path.delimiter);
|
|
362
|
+
return pathEntries.some((p) => p && path.resolve(p) === NPM_GLOBAL_BIN);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function getUpgradeCommand(): string {
|
|
366
|
+
// Install into a per-user prefix so the upgrade never needs root and
|
|
367
|
+
// always lands under the home directory instead of a system npm dir.
|
|
368
|
+
return `npm install -g ${PACKAGE_NAME}@latest --prefer-online --prefix "${NPM_GLOBAL_PREFIX}"`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
*
|
|
373
|
+
*/
|
|
374
|
+
export async function performUpgrade(): Promise<{ success: boolean; message: string }> {
|
|
375
|
+
const { spawn } = await import('child_process');
|
|
376
|
+
|
|
377
|
+
await fsPromises.mkdir(NPM_GLOBAL_PREFIX, { recursive: true }).catch(() => {});
|
|
378
|
+
|
|
379
|
+
return new Promise((resolve) => {
|
|
380
|
+
const command = getUpgradeCommand();
|
|
381
|
+
|
|
382
|
+
const child = spawn(command, {
|
|
383
|
+
stdio: 'inherit',
|
|
384
|
+
shell: true,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
child.on('close', (code) => {
|
|
388
|
+
if (code === 0) {
|
|
389
|
+
const pathWarning = isPrefixBinInPath()
|
|
390
|
+
? ''
|
|
391
|
+
: `\n⚠️ Add ${NPM_GLOBAL_BIN} to your PATH to use the updated '${PACKAGE_NAME}' command, e.g.:\n export PATH="${NPM_GLOBAL_BIN}:$PATH"`;
|
|
392
|
+
resolve({
|
|
393
|
+
success: true,
|
|
394
|
+
message: `✅ Upgrade successful! Restarting...${pathWarning}`,
|
|
395
|
+
});
|
|
396
|
+
} else {
|
|
397
|
+
resolve({
|
|
398
|
+
success: false,
|
|
399
|
+
message: `❌ Upgrade failed (exit code: ${code})`,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
child.on('error', (error) => {
|
|
405
|
+
resolve({
|
|
406
|
+
success: false,
|
|
407
|
+
message: `❌ Upgrade failed: ${error.message}`,
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
*
|
|
415
|
+
*
|
|
416
|
+
*/
|
|
417
|
+
export function restartApp(): void {
|
|
418
|
+
// 启动新的 aegis 进
|
|
419
|
+
// 使用 detached: true 让子进程独立运
|
|
420
|
+
// 使用 stdio: 'inherit' 让新进程继承终
|
|
421
|
+
const childEnv = { ...process.env };
|
|
422
|
+
if (!isPrefixBinInPath()) {
|
|
423
|
+
childEnv.PATH = `${NPM_GLOBAL_BIN}${path.delimiter}${childEnv.PATH || ''}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const child = spawn(PACKAGE_NAME, process.argv.slice(2), {
|
|
427
|
+
stdio: 'inherit',
|
|
428
|
+
shell: true,
|
|
429
|
+
detached: true,
|
|
430
|
+
env: childEnv,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// 让子进程脱离父进
|
|
434
|
+
child.unref();
|
|
435
|
+
|
|
436
|
+
// 退出当前进
|
|
437
|
+
process.exit(0);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
*
|
|
442
|
+
*/
|
|
443
|
+
export function getCurrentVersion(): string {
|
|
444
|
+
return CURRENT_VERSION;
|
|
445
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Services 模块导出
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// VersionChecker
|
|
6
|
+
export {
|
|
7
|
+
checkVersion,
|
|
8
|
+
checkVersionOnStartup,
|
|
9
|
+
setSkipUntilVersion,
|
|
10
|
+
clearSkipVersion,
|
|
11
|
+
getUpgradeCommand,
|
|
12
|
+
performUpgrade,
|
|
13
|
+
getCurrentVersion,
|
|
14
|
+
} from './VersionChecker.js';
|
|
15
|
+
|
|
16
|
+
export type { VersionCheckResult } from './VersionChecker.js';
|
|
17
|
+
|
|
18
|
+
// ChatService
|
|
19
|
+
export {
|
|
20
|
+
OpenAIChatService,
|
|
21
|
+
createChatService,
|
|
22
|
+
} from './ChatService.js';
|
|
23
|
+
|
|
24
|
+
export type { ChatServiceConfig } from './ChatService.js';
|
|
25
|
+
|
|
26
|
+
// Streaming
|
|
27
|
+
export {
|
|
28
|
+
parseStreamEvent,
|
|
29
|
+
TranscriptBuffer,
|
|
30
|
+
ThrottledRenderer,
|
|
31
|
+
TERMINAL_FORMATTING,
|
|
32
|
+
PLAIN_TEXT_FORMATTING,
|
|
33
|
+
buildRenderContext,
|
|
34
|
+
TERMINAL_PROFILE,
|
|
35
|
+
PLAIN_TEXT_PROFILE,
|
|
36
|
+
COMPACT_TERMINAL_PROFILE,
|
|
37
|
+
buildRenderingProfile,
|
|
38
|
+
getStatusForEvent,
|
|
39
|
+
STATUS_MESSAGE_PREFIXES,
|
|
40
|
+
} from './streaming/index.js';
|
|
41
|
+
export type {
|
|
42
|
+
AnthropicStreamEvent,
|
|
43
|
+
ContentBlockStartEvent,
|
|
44
|
+
ContentBlockDeltaEvent,
|
|
45
|
+
ContentBlockStopEvent,
|
|
46
|
+
MessageStartEvent,
|
|
47
|
+
MessageDeltaEvent,
|
|
48
|
+
MessageStopEvent,
|
|
49
|
+
ParsedEvent,
|
|
50
|
+
ParsedEventType,
|
|
51
|
+
StreamSegment,
|
|
52
|
+
SegmentKind,
|
|
53
|
+
RenderContext,
|
|
54
|
+
RenderFormatting,
|
|
55
|
+
RenderingProfile,
|
|
56
|
+
} from './streaming/types.js';
|
|
57
|
+
export { TRANSCRIPT_EVENT_TYPES } from './streaming/types.js';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAIEventAdapter — converts OpenAI streaming delta chunks to AnthropicStreamEvent[].
|
|
3
|
+
*
|
|
4
|
+
* OpenAI streaming delivers flat delta objects (delta.content, delta.tool_calls[]).
|
|
5
|
+
* The rest of the streaming pipeline speaks Anthropic's content_block model
|
|
6
|
+
* (content_block_start / content_block_delta / content_block_stop).
|
|
7
|
+
*
|
|
8
|
+
* This stateful adapter bridges the two formats so StreamEventParser +
|
|
9
|
+
* TranscriptBuffer can work regardless of which backend is in use.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { AnthropicStreamEvent } from './types.js';
|
|
13
|
+
|
|
14
|
+
export class OpenAIEventAdapter {
|
|
15
|
+
private _textBlockOpen = false;
|
|
16
|
+
private _thinkingBlockOpen = false;
|
|
17
|
+
private _textIdx = 0;
|
|
18
|
+
private _thinkingIdx = 1;
|
|
19
|
+
private _nextToolIdx = 2;
|
|
20
|
+
/** OpenAI tool call index → { id, name, blockIdx } */
|
|
21
|
+
private _toolBlocks = new Map<number, { id: string; name: string; blockIdx: number }>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert one OpenAI streaming chunk delta into zero or more AnthropicStreamEvents.
|
|
25
|
+
* Call once per chunk, in arrival order.
|
|
26
|
+
*/
|
|
27
|
+
adapt(delta: {
|
|
28
|
+
content?: string | null;
|
|
29
|
+
reasoning_content?: string | null;
|
|
30
|
+
thinking?: string | null;
|
|
31
|
+
reasoning?: string | null;
|
|
32
|
+
tool_calls?: Array<{
|
|
33
|
+
index: number;
|
|
34
|
+
id?: string;
|
|
35
|
+
function?: { name?: string; arguments?: string };
|
|
36
|
+
}> | null;
|
|
37
|
+
}): AnthropicStreamEvent[] {
|
|
38
|
+
const events: AnthropicStreamEvent[] = [];
|
|
39
|
+
|
|
40
|
+
// Text content
|
|
41
|
+
if (delta.content) {
|
|
42
|
+
if (!this._textBlockOpen) {
|
|
43
|
+
events.push({
|
|
44
|
+
type: 'content_block_start',
|
|
45
|
+
index: this._textIdx,
|
|
46
|
+
content_block: { type: 'text', text: '' },
|
|
47
|
+
});
|
|
48
|
+
this._textBlockOpen = true;
|
|
49
|
+
}
|
|
50
|
+
events.push({
|
|
51
|
+
type: 'content_block_delta',
|
|
52
|
+
index: this._textIdx,
|
|
53
|
+
delta: { type: 'text_delta', text: delta.content },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Thinking / reasoning content (multiple field names across providers)
|
|
58
|
+
const thinking = delta.reasoning_content ?? delta.thinking ?? delta.reasoning;
|
|
59
|
+
if (thinking) {
|
|
60
|
+
if (!this._thinkingBlockOpen) {
|
|
61
|
+
events.push({
|
|
62
|
+
type: 'content_block_start',
|
|
63
|
+
index: this._thinkingIdx,
|
|
64
|
+
content_block: { type: 'thinking', thinking: '' },
|
|
65
|
+
});
|
|
66
|
+
this._thinkingBlockOpen = true;
|
|
67
|
+
}
|
|
68
|
+
events.push({
|
|
69
|
+
type: 'content_block_delta',
|
|
70
|
+
index: this._thinkingIdx,
|
|
71
|
+
delta: { type: 'thinking_delta', thinking },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Tool calls
|
|
76
|
+
if (delta.tool_calls) {
|
|
77
|
+
for (const tc of delta.tool_calls) {
|
|
78
|
+
const oi = tc.index;
|
|
79
|
+
if (!this._toolBlocks.has(oi) && tc.id && tc.function?.name) {
|
|
80
|
+
const blockIdx = this._nextToolIdx++;
|
|
81
|
+
this._toolBlocks.set(oi, { id: tc.id, name: tc.function.name, blockIdx });
|
|
82
|
+
events.push({
|
|
83
|
+
type: 'content_block_start',
|
|
84
|
+
index: blockIdx,
|
|
85
|
+
content_block: { type: 'tool_use', id: tc.id, name: tc.function.name, input: {} },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const tool = this._toolBlocks.get(oi);
|
|
89
|
+
if (tool && tc.function?.arguments) {
|
|
90
|
+
events.push({
|
|
91
|
+
type: 'content_block_delta',
|
|
92
|
+
index: tool.blockIdx,
|
|
93
|
+
delta: { type: 'input_json_delta', partial_json: tc.function.arguments },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return events;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Emit stop events for all open blocks, then message_stop.
|
|
104
|
+
* Call once after the stream ends.
|
|
105
|
+
*/
|
|
106
|
+
finalize(stopReason?: string): AnthropicStreamEvent[] {
|
|
107
|
+
const events: AnthropicStreamEvent[] = [];
|
|
108
|
+
if (this._textBlockOpen) {
|
|
109
|
+
events.push({ type: 'content_block_stop', index: this._textIdx });
|
|
110
|
+
}
|
|
111
|
+
if (this._thinkingBlockOpen) {
|
|
112
|
+
events.push({ type: 'content_block_stop', index: this._thinkingIdx });
|
|
113
|
+
}
|
|
114
|
+
for (const { blockIdx } of this._toolBlocks.values()) {
|
|
115
|
+
events.push({ type: 'content_block_stop', index: blockIdx });
|
|
116
|
+
}
|
|
117
|
+
if (stopReason) {
|
|
118
|
+
events.push({
|
|
119
|
+
type: 'message_delta',
|
|
120
|
+
delta: { stop_reason: stopReason, stop_sequence: null },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
events.push({ type: 'message_stop' });
|
|
124
|
+
return events;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RenderingProfile — platform-specific rendering configuration.
|
|
3
|
+
*
|
|
4
|
+
* Replicates free-claude-code's messaging/rendering/profiles.py.
|
|
5
|
+
* Bundles format_status function, parse_mode, render context, and
|
|
6
|
+
* character limits into a single profile for a given target format.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { RenderingProfile } from './types.js';
|
|
10
|
+
import {
|
|
11
|
+
TERMINAL_FORMATTING,
|
|
12
|
+
PLAIN_TEXT_FORMATTING,
|
|
13
|
+
buildRenderContext,
|
|
14
|
+
} from './renderFormatting.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Format a status line: emoji + bold(label) [+ suffix].
|
|
18
|
+
* Replicates the format_status function from discord/telegram markdown modules.
|
|
19
|
+
*/
|
|
20
|
+
function formatStatus(
|
|
21
|
+
emoji: string,
|
|
22
|
+
label: string,
|
|
23
|
+
suffix?: string,
|
|
24
|
+
formatting: typeof TERMINAL_FORMATTING = TERMINAL_FORMATTING,
|
|
25
|
+
): string {
|
|
26
|
+
const base = `${emoji} ${formatting.bold(label)}`;
|
|
27
|
+
if (suffix) {
|
|
28
|
+
return `${base} ${formatting.escapeText(suffix)}`;
|
|
29
|
+
}
|
|
30
|
+
return base;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Status formatter using terminal formatting */
|
|
34
|
+
function termFormatStatus(emoji: string, label: string, suffix?: string): string {
|
|
35
|
+
return formatStatus(emoji, label, suffix, TERMINAL_FORMATTING);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Status formatter using plain text (no ANSI) */
|
|
39
|
+
function plainFormatStatus(emoji: string, label: string, suffix?: string): string {
|
|
40
|
+
return formatStatus(emoji, label, suffix, PLAIN_TEXT_FORMATTING);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ==================== Pre-built Profiles ====================
|
|
44
|
+
|
|
45
|
+
/** Terminal rendering profile (ANSI bold, backtick code, unlimited chars) */
|
|
46
|
+
export const TERMINAL_PROFILE: RenderingProfile = {
|
|
47
|
+
formatStatus: termFormatStatus,
|
|
48
|
+
parseMode: null,
|
|
49
|
+
renderCtx: buildRenderContext({ formatting: TERMINAL_FORMATTING, limitChars: 100000 }),
|
|
50
|
+
limitChars: 100000,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Plain text rendering profile (no ANSI, backtick code, suitable for logging) */
|
|
54
|
+
export const PLAIN_TEXT_PROFILE: RenderingProfile = {
|
|
55
|
+
formatStatus: plainFormatStatus,
|
|
56
|
+
parseMode: null,
|
|
57
|
+
renderCtx: buildRenderContext({ formatting: PLAIN_TEXT_FORMATTING, limitChars: 100000 }),
|
|
58
|
+
limitChars: 100000,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Compact terminal profile — tighter limits for constrained displays */
|
|
62
|
+
export const COMPACT_TERMINAL_PROFILE: RenderingProfile = {
|
|
63
|
+
formatStatus: termFormatStatus,
|
|
64
|
+
parseMode: null,
|
|
65
|
+
renderCtx: buildRenderContext({
|
|
66
|
+
formatting: TERMINAL_FORMATTING,
|
|
67
|
+
limitChars: 4000,
|
|
68
|
+
thinkingTailMax: 500,
|
|
69
|
+
textTailMax: 1000,
|
|
70
|
+
toolOutputTailMax: 800,
|
|
71
|
+
}),
|
|
72
|
+
limitChars: 4000,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build a rendering profile by platform name.
|
|
77
|
+
* Replicates build_rendering_profile() from profiles.py.
|
|
78
|
+
*/
|
|
79
|
+
export function buildRenderingProfile(platformName: string): RenderingProfile {
|
|
80
|
+
switch (platformName) {
|
|
81
|
+
case 'terminal':
|
|
82
|
+
return TERMINAL_PROFILE;
|
|
83
|
+
case 'compact':
|
|
84
|
+
return COMPACT_TERMINAL_PROFILE;
|
|
85
|
+
case 'plain':
|
|
86
|
+
return PLAIN_TEXT_PROFILE;
|
|
87
|
+
default:
|
|
88
|
+
return TERMINAL_PROFILE;
|
|
89
|
+
}
|
|
90
|
+
}
|