snow-ai 0.3.11 → 0.3.13

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.
@@ -0,0 +1,159 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { logger } from './logger.js';
5
+ class HistoryManager {
6
+ constructor() {
7
+ Object.defineProperty(this, "historyFile", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: void 0
12
+ });
13
+ Object.defineProperty(this, "maxAge", {
14
+ enumerable: true,
15
+ configurable: true,
16
+ writable: true,
17
+ value: 24 * 60 * 60 * 1000
18
+ }); // 1 day in milliseconds
19
+ Object.defineProperty(this, "maxEntries", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: 1000
24
+ }); // Maximum number of entries to keep
25
+ Object.defineProperty(this, "historyData", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: null
30
+ });
31
+ const snowDir = path.join(os.homedir(), '.snow');
32
+ this.historyFile = path.join(snowDir, 'history.json');
33
+ }
34
+ /**
35
+ * Ensure the .snow directory exists
36
+ */
37
+ async ensureSnowDir() {
38
+ try {
39
+ const snowDir = path.dirname(this.historyFile);
40
+ await fs.mkdir(snowDir, { recursive: true });
41
+ }
42
+ catch (error) {
43
+ // Directory already exists or other error
44
+ }
45
+ }
46
+ /**
47
+ * Load history from file
48
+ */
49
+ async loadHistory() {
50
+ try {
51
+ await this.ensureSnowDir();
52
+ // Try to read existing history file
53
+ const data = await fs.readFile(this.historyFile, 'utf-8');
54
+ this.historyData = JSON.parse(data);
55
+ // Clean up old entries if needed
56
+ await this.cleanupOldEntries();
57
+ return this.historyData.entries;
58
+ }
59
+ catch (error) {
60
+ // File doesn't exist or is corrupted, start fresh
61
+ this.historyData = {
62
+ entries: [],
63
+ lastCleanup: Date.now(),
64
+ };
65
+ return [];
66
+ }
67
+ }
68
+ /**
69
+ * Add a new entry to history
70
+ */
71
+ async addEntry(content) {
72
+ // Don't add empty or whitespace-only entries
73
+ if (!content || !content.trim()) {
74
+ return;
75
+ }
76
+ // Load history if not already loaded
77
+ if (!this.historyData) {
78
+ await this.loadHistory();
79
+ }
80
+ // Don't add duplicate of the last entry
81
+ const lastEntry = this.historyData.entries[this.historyData.entries.length - 1];
82
+ if (lastEntry && lastEntry.content === content) {
83
+ return;
84
+ }
85
+ // Add new entry
86
+ const newEntry = {
87
+ content,
88
+ timestamp: Date.now(),
89
+ };
90
+ this.historyData.entries.push(newEntry);
91
+ // Limit the number of entries
92
+ if (this.historyData.entries.length > this.maxEntries) {
93
+ this.historyData.entries = this.historyData.entries.slice(-this.maxEntries);
94
+ }
95
+ // Save to file
96
+ await this.saveHistory();
97
+ }
98
+ /**
99
+ * Get all history entries (newest first)
100
+ */
101
+ async getEntries() {
102
+ if (!this.historyData) {
103
+ await this.loadHistory();
104
+ }
105
+ // Return a copy in reverse order (newest first)
106
+ return [...this.historyData.entries].reverse();
107
+ }
108
+ /**
109
+ * Clean up entries older than maxAge
110
+ */
111
+ async cleanupOldEntries() {
112
+ if (!this.historyData) {
113
+ return;
114
+ }
115
+ const now = Date.now();
116
+ const cutoffTime = now - this.maxAge;
117
+ // Only cleanup once per hour to avoid excessive file writes
118
+ if (now - this.historyData.lastCleanup < 60 * 60 * 1000) {
119
+ return;
120
+ }
121
+ // Filter out old entries
122
+ const originalLength = this.historyData.entries.length;
123
+ this.historyData.entries = this.historyData.entries.filter(entry => entry.timestamp > cutoffTime);
124
+ // Update last cleanup time
125
+ this.historyData.lastCleanup = now;
126
+ // Save if we removed any entries
127
+ if (this.historyData.entries.length < originalLength) {
128
+ await this.saveHistory();
129
+ logger.debug(`Cleaned up ${originalLength - this.historyData.entries.length} old history entries`);
130
+ }
131
+ }
132
+ /**
133
+ * Save history to file
134
+ */
135
+ async saveHistory() {
136
+ if (!this.historyData) {
137
+ return;
138
+ }
139
+ try {
140
+ await this.ensureSnowDir();
141
+ await fs.writeFile(this.historyFile, JSON.stringify(this.historyData, null, 2), 'utf-8');
142
+ }
143
+ catch (error) {
144
+ logger.error('Failed to save history:', error);
145
+ }
146
+ }
147
+ /**
148
+ * Clear all history
149
+ */
150
+ async clearHistory() {
151
+ this.historyData = {
152
+ entries: [],
153
+ lastCleanup: Date.now(),
154
+ };
155
+ await this.saveHistory();
156
+ }
157
+ }
158
+ // Export singleton instance
159
+ export const historyManager = new HistoryManager();
@@ -75,7 +75,7 @@ export async function executeSubAgent(agentId, prompt, onMessage, abortSignal, r
75
75
  // Get API configuration
76
76
  const config = getOpenAiConfig();
77
77
  const currentSession = sessionManager.getCurrentSession();
78
- const model = config.advancedModel || 'claude-3-5-sonnet-20241022';
78
+ const model = config.advancedModel || 'gpt-5';
79
79
  // Call API with sub-agent's tools - choose API based on config
80
80
  const stream = config.requestMethod === 'anthropic'
81
81
  ? createStreamingAnthropicCompletion({
@@ -85,6 +85,7 @@ export async function executeSubAgent(agentId, prompt, onMessage, abortSignal, r
85
85
  max_tokens: config.maxTokens || 4096,
86
86
  tools: allowedTools,
87
87
  sessionId: currentSession?.id,
88
+ disableThinking: true, // Sub-agents 不使用 Extended Thinking
88
89
  }, abortSignal)
89
90
  : config.requestMethod === 'gemini'
90
91
  ? createStreamingGeminiCompletion({
@@ -196,16 +196,19 @@ export class TextBuffer {
196
196
  if (this.pasteTimer) {
197
197
  clearTimeout(this.pasteTimer);
198
198
  }
199
- // 如果是第一批数据,记录插入位置
199
+ // 如果是第一批数据,记录插入位置并清空内容
200
200
  const isFirstBatch = !this.pasteAccumulator;
201
201
  if (isFirstBatch) {
202
202
  this.pastePlaceholderPosition = this.cursorIndex;
203
+ // 保存粘贴位置前后的内容,避免后续计算错误
204
+ this.content = cpSlice(this.content, 0, this.pastePlaceholderPosition) +
205
+ cpSlice(this.content, this.pastePlaceholderPosition);
203
206
  }
204
207
  // 累积数据
205
208
  this.pasteAccumulator += sanitized;
206
- // 移除旧的临时占位符(如果存在)
209
+ // 移除所有旧的临时占位符(使用全局替换)
207
210
  if (!isFirstBatch) {
208
- const tempPlaceholderPattern = /\[Pasting\.\.\. \d+ chars\]/;
211
+ const tempPlaceholderPattern = /\[Pasting\.\.\. \d+ chars\]/g;
209
212
  this.content = this.content.replace(tempPlaceholderPattern, '');
210
213
  }
211
214
  // 显示更新后的临时占位符
@@ -241,9 +244,9 @@ export class TextBuffer {
241
244
  return;
242
245
  }
243
246
  const totalChars = this.pasteAccumulator.length;
244
- // 移除临时占位符(如果存在)
247
+ // 移除所有临时占位符(使用全局替换)
245
248
  // 临时占位符格式: [Pasting... XXX chars]
246
- const tempPlaceholderPattern = /\[Pasting\.\.\. \d+ chars\]/;
249
+ const tempPlaceholderPattern = /\[Pasting\.\.\. \d+ chars\]/g;
247
250
  this.content = this.content.replace(tempPlaceholderPattern, '');
248
251
  // 只有当累积的字符数超过300时才创建占位符
249
252
  if (totalChars > 300) {
@@ -331,6 +334,14 @@ export class TextBuffer {
331
334
  if (this.visualLines.length === 0) {
332
335
  return;
333
336
  }
337
+ // 检查是否只有单行(没有换行符)
338
+ const hasNewline = this.content.includes('\n');
339
+ if (!hasNewline && this.visualLines.length === 1) {
340
+ // 单行模式:移动到行首
341
+ this.cursorIndex = 0;
342
+ this.recomputeVisualCursorOnly();
343
+ return;
344
+ }
334
345
  const currentRow = this.visualCursorPos[0];
335
346
  if (currentRow <= 0) {
336
347
  return;
@@ -341,6 +352,14 @@ export class TextBuffer {
341
352
  if (this.visualLines.length === 0) {
342
353
  return;
343
354
  }
355
+ // 检查是否只有单行(没有换行符)
356
+ const hasNewline = this.content.includes('\n');
357
+ if (!hasNewline && this.visualLines.length === 1) {
358
+ // 单行模式:移动到行尾
359
+ this.cursorIndex = cpLen(this.content);
360
+ this.recomputeVisualCursorOnly();
361
+ return;
362
+ }
344
363
  const currentRow = this.visualCursorPos[0];
345
364
  if (currentRow >= this.visualLines.length - 1) {
346
365
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {