hyacinth-ai 0.9.8 → 0.9.9

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.
@@ -255,7 +255,7 @@ export async function createAgent(options, supervisor) {
255
255
  description: '会话临时工具',
256
256
  getContent: () => {
257
257
  const names = toolRegistry.getHotAddedNames();
258
- return names.length > 0 ? `[Session-only tools]\n${names.join(', ')}` : '';
258
+ return names.length > 0 ? `[Hot-added tools (session-scoped, additional to standard tools)]\n${names.join(', ')}` : '';
259
259
  },
260
260
  });
261
261
  const channelRegistry = new ModelChannelRegistry(cwd);
@@ -1415,6 +1415,10 @@ export async function runTui(provider, sessionId, shouldContinue, maxTurns, maxC
1415
1415
  // 就地切换,不重启
1416
1416
  await loop.switchSession(dir);
1417
1417
  sessionDir = dir;
1418
+ lastTurnCount = 0;
1419
+ lastTokensUsed = 0;
1420
+ const newStats = await statsManager.get(dir);
1421
+ refreshStatus(loop.getTurnInfo(newStats.turn_count ?? 0, newStats.current_context_tokens ?? 0));
1418
1422
  chatLog.clearAll();
1419
1423
  replayEvents(chatLog, dir);
1420
1424
  chatLog.addSystem(theme.success(`已切换到 session ${sessionId}`));
@@ -2460,6 +2464,10 @@ export async function runTui(provider, sessionId, shouldContinue, maxTurns, maxC
2460
2464
  await loop.switchSession(newSessionDir);
2461
2465
  sessionDir = newSessionDir;
2462
2466
  preciseModeActive = true;
2467
+ lastTurnCount = 0;
2468
+ lastTokensUsed = 0;
2469
+ const psStats = await statsManager.get(newSessionDir);
2470
+ refreshStatus(loop.getTurnInfo(psStats.turn_count ?? 0, psStats.current_context_tokens ?? 0));
2463
2471
  const label = existingPrecise ? '恢复已有' : '新建';
2464
2472
  chatLog.addSystem(theme.success(`精确模式已开启 — ${label} session: ${newSession.id}`));
2465
2473
  chatLog.clearAll();
@@ -2478,6 +2486,10 @@ export async function runTui(provider, sessionId, shouldContinue, maxTurns, maxC
2478
2486
  await loop.switchSession(originalSessionDir);
2479
2487
  sessionDir = originalSessionDir;
2480
2488
  originalSessionDir = null;
2489
+ lastTurnCount = 0;
2490
+ lastTokensUsed = 0;
2491
+ const origStats = await statsManager.get(sessionDir);
2492
+ refreshStatus(loop.getTurnInfo(origStats.turn_count ?? 0, origStats.current_context_tokens ?? 0));
2481
2493
  chatLog.clearAll();
2482
2494
  replayEvents(chatLog, sessionDir);
2483
2495
  }
@@ -20,8 +20,9 @@ export function watchTools(deps) {
20
20
  }
21
21
  catch { /* ignore */ }
22
22
  const fileMap = new Map();
23
+ const mtimeMap = new Map();
23
24
  // 初始扫描不标记 hotAdded — 工具应进入 Zone 2 tool_rules
24
- scanAndSync(toolsDir, toolRegistry, fileMap, true);
25
+ scanAndSync(toolsDir, toolRegistry, fileMap, mtimeMap, true);
25
26
  let debounceTimer = null;
26
27
  logger.info(`Watching tools directory: ${toolsDir}`);
27
28
  const watcher = fs.watch(toolsDir, { recursive: true }, (_eventType, filename) => {
@@ -32,20 +33,32 @@ export function watchTools(deps) {
32
33
  if (debounceTimer)
33
34
  clearTimeout(debounceTimer);
34
35
  // 文件变更触发的重新扫描 — 此时才标记 hotAdded,工具进 Zone 5 session-tools
35
- debounceTimer = setTimeout(() => scanAndSync(toolsDir, toolRegistry, fileMap, false), debounceMs);
36
+ debounceTimer = setTimeout(() => scanAndSync(toolsDir, toolRegistry, fileMap, mtimeMap, false), debounceMs);
36
37
  });
37
38
  watcher.on('error', (err) => {
38
39
  logger.warn(`Tool watcher error: ${err.message}`, { error: err.message });
39
40
  });
40
41
  return watcher;
41
42
  }
42
- async function scanAndSync(dir, registry, fileMap, isInitialScan) {
43
+ async function scanAndSync(dir, registry, fileMap, mtimeMap, isInitialScan) {
43
44
  const resolved = resolveToolEntries(dir);
44
45
  const currentKeys = new Set(resolved.map((r) => r.trackingKey));
45
46
  for (const entry of resolved) {
46
47
  const knownToolName = fileMap.get(entry.trackingKey);
47
48
  if (knownToolName !== undefined) {
48
- // 已跟踪 → 重新加载(文件变更触发的热重载)
49
+ // 已跟踪 → 检查文件是否真的变了,避免 fs.watch 误触发导致全部标记为 hot-added
50
+ let mtime;
51
+ try {
52
+ mtime = fs.statSync(entry.filePath).mtimeMs;
53
+ }
54
+ catch { /* stat 失败则走重载 */ }
55
+ if (mtime !== undefined) {
56
+ const prevMtime = mtimeMap.get(entry.trackingKey);
57
+ if (prevMtime === mtime)
58
+ continue; // 文件没变,跳过
59
+ mtimeMap.set(entry.trackingKey, mtime);
60
+ }
61
+ // 重新加载(文件变更触发的热重载)
49
62
  if (entry.factory) {
50
63
  // Python 工具:不再解析
51
64
  const tool = entry.factory();
@@ -79,13 +92,14 @@ async function scanAndSync(dir, registry, fileMap, isInitialScan) {
79
92
  }
80
93
  }
81
94
  else {
82
- await loadAndRegister(entry, registry, fileMap, isInitialScan);
95
+ await loadAndRegister(entry, registry, fileMap, mtimeMap, isInitialScan);
83
96
  }
84
97
  }
85
98
  for (const [key, toolName] of fileMap.entries()) {
86
99
  if (!currentKeys.has(key)) {
87
100
  registry.unregister(toolName);
88
101
  fileMap.delete(key);
102
+ mtimeMap.delete(key);
89
103
  logger.info(`Tool unregistered: ${toolName} (${key})`);
90
104
  }
91
105
  }
@@ -138,7 +152,7 @@ function resolveToolEntries(dir) {
138
152
  }
139
153
  return entries;
140
154
  }
141
- async function loadAndRegister(entry, registry, fileMap, isInitialScan) {
155
+ async function loadAndRegister(entry, registry, fileMap, mtimeMap, isInitialScan) {
142
156
  try {
143
157
  let tool;
144
158
  if (entry.factory) {
@@ -159,6 +173,10 @@ async function loadAndRegister(entry, registry, fileMap, isInitialScan) {
159
173
  if (!isInitialScan)
160
174
  registry.markHotAdded(tool.name);
161
175
  fileMap.set(entry.trackingKey, tool.name);
176
+ try {
177
+ mtimeMap.set(entry.trackingKey, fs.statSync(entry.filePath).mtimeMs);
178
+ }
179
+ catch { }
162
180
  logger.info(`Tool registered: ${tool.name} (${entry.label})`);
163
181
  }
164
182
  catch (err) {
@@ -4,4 +4,5 @@ _这里是陪伴模式的默认人设。_
4
4
 
5
5
  你可以编辑此文件(`~/.agent/prompts/persona/PartnerSoul.md`)来定制陪伴者的性格和风格。不编辑也可以正常使用陪伴模式。
6
6
 
7
- 向用户询问,得到身份后覆盖当前文件内容
7
+ 必须立刻向用户询问,得到完整的人格后向用户确认并覆盖当前文件内容
8
+
package/null ADDED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyacinth-ai",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "Hyacinth 🪻 — multi-provider AI agent with tool system and context management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",