hyacinth-ai 0.9.9 → 0.9.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.
@@ -215,12 +215,18 @@ export class HttpWebhookChannel {
215
215
  const { TuiWsSession } = await import('./tui-ws-session.js');
216
216
  const { randomBytes } = await import('node:crypto');
217
217
  const tuiWss = new WebSocketServer({ noServer: true });
218
+ const desktopWss = new WebSocketServer({ noServer: true });
218
219
  this.app.server.on('upgrade', (request, socket, head) => {
219
220
  if (request.url === '/tui') {
220
221
  tuiWss.handleUpgrade(request, socket, head, (ws) => {
221
222
  tuiWss.emit('connection', ws, request);
222
223
  });
223
224
  }
225
+ else if (request.url === '/desktop') {
226
+ desktopWss.handleUpgrade(request, socket, head, (ws) => {
227
+ desktopWss.emit('connection', ws, request);
228
+ });
229
+ }
224
230
  else {
225
231
  socket.destroy();
226
232
  }
@@ -247,6 +253,32 @@ export class HttpWebhookChannel {
247
253
  logger.info('TUI WS client disconnected', { sessionId });
248
254
  });
249
255
  });
256
+ // ── Desktop GUI WebSocket 端点 ──
257
+ desktopWss.on('connection', (ws) => {
258
+ const now = new Date();
259
+ const date = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
260
+ const time = `${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
261
+ const sessionId = `webui_${date}-${time}-${randomBytes(3).toString('hex')}`;
262
+ const session = new TuiWsSession(ws, sessionId);
263
+ logger.info('Desktop WS client connected', { sessionId });
264
+ if (this.wsAgentFactory) {
265
+ session.initialize(this.wsAgentFactory).catch((err) => {
266
+ logger.error('Desktop WS init failed', err instanceof Error ? err : new Error(String(err)));
267
+ });
268
+ }
269
+ else {
270
+ logger.warn('Desktop WS agentFactory not available');
271
+ }
272
+ ws.on('message', (data) => {
273
+ session.handleMessage(data).catch((err) => {
274
+ logger.error('Desktop WS msg error', err instanceof Error ? err : new Error(String(err)));
275
+ });
276
+ });
277
+ ws.on('close', () => {
278
+ session.close().catch(() => { });
279
+ logger.info('Desktop WS client disconnected', { sessionId });
280
+ });
281
+ });
250
282
  // ── Start ───────────────────────────────────────────────────
251
283
  await this.app.listen({ port: port, host: host });
252
284
  this.status = 'active';
@@ -134,7 +134,7 @@ async function resolveRuntime(sec, ctx) {
134
134
  // # currentDate 是系统元数据标记(非用户输入),模型训练数据中识别为背景信息
135
135
  const [datePart, timePart] = ctx.timestamp.split(' ');
136
136
  const dateSlash = datePart.replace(/-/g, '/');
137
- return `# currentDate\nToday is ${dateSlash}, ${timePart}.`;
137
+ return `# currentDate\n(系统提供) Today is ${dateSlash}, ${timePart}.`;
138
138
  }
139
139
  if (src === 'runtime:userInput') {
140
140
  return ctx.userInput || undefined;
@@ -27,7 +27,7 @@ import { MemoryStore } from '../memory/memory-store.js';
27
27
  import { AgentLoop } from '../orchestrator/loop.js';
28
28
  import { PluginManager } from '../plugins/index.js';
29
29
  import { initDependencyAnalyzer } from '../dependency/index.js';
30
- import { ensureGlobalPersonaFiles, getBootstrapStatus } from '../setup/persona-bootstrap.js';
30
+ import { ensureGlobalPersonaFiles, ensureGlobalPromptDir, getBootstrapStatus } from '../setup/persona-bootstrap.js';
31
31
  import { ConfigManager } from '../setup/config.js';
32
32
  import { RuntimeConfigCenter } from '../runtime/config-center.js';
33
33
  import { getDefaultConfig } from '../runtime/defaults.js';
@@ -119,6 +119,17 @@ export async function createAgent(options, supervisor) {
119
119
  const personaSetup = await ensureGlobalPersonaFiles();
120
120
  const effectivePersonaDir = personaDir ?? personaSetup.personaDir;
121
121
  const effectiveBootstrapStatus = bootstrapStatus ?? (await getBootstrapStatus(effectivePersonaDir));
122
+ // ── 内置 Prompt 同步 ─────────────────────────────────────────────
123
+ // 将所有内置 prompt 同步到 ~/.agent/prompts/ 下,
124
+ // 使得 loadPrompt() 的查找优先级正确:外部覆盖 > 内置兜底。
125
+ await ensureGlobalPromptDir('attention');
126
+ await ensureGlobalPromptDir('tools');
127
+ await ensureGlobalPromptDir('agents');
128
+ await ensureGlobalPromptDir('flows');
129
+ await ensureGlobalPromptDir('skills');
130
+ await ensureGlobalPromptDir('precise');
131
+ await ensureGlobalPromptDir('environment');
132
+ // root 级文件 summary.md 由 loadPrompt 递归搜索找到,暂不单独同步
122
133
  // ── 注册 Flow(Bootstrap + TODO + Spec)──────────────────────────
123
134
  flowRegistry.register(new BootstrapFlow(effectivePersonaDir));
124
135
  flowRegistry.register(new TodoFlow());
@@ -165,7 +165,7 @@ export class StructuredStore {
165
165
  score += Math.round(3 * weight);
166
166
  }
167
167
  }
168
- if (score > 0) {
168
+ if (matchedTags.length > 0) {
169
169
  results.push({ entry, score, matchedTags });
170
170
  }
171
171
  }
@@ -237,7 +237,7 @@ export class StructuredStore {
237
237
  const refs = results.slice(maxMain, maxMain + maxRefs);
238
238
  for (const r of main) {
239
239
  const tags = r.matchedTags.length > 0 ? `匹配标签: ${r.matchedTags.join(', ')}` : 'FTS5 匹配';
240
- lines.push(`━━ ${r.entry.id} ── [${r.entry.category}] ── ${tags}`);
240
+ lines.push(`(知识库提供) ━━ ${r.entry.id} ── [${r.entry.category}] ── ${tags}`);
241
241
  lines.push(r.entry.content);
242
242
  if (r.entry.ctx_before)
243
243
  lines.push(`前置: ${r.entry.ctx_before}`);
@@ -1,5 +1,6 @@
1
1
  注意事项:
2
2
  - 减少不必要的对话轮次:能并行的独立工具调用一次发出,不要逐个等待
3
+ - 子Agent 注册一次即可反复 delegate,不要重复 create_sub_agent。
3
4
  - 修改代码前先阅读相关代码,理解上下文
4
5
  - 优先使用现有工具和函数,避免重复实现
5
6
  - 执行命令时注意安全性,避免破坏性操作
@@ -10,3 +11,4 @@
10
11
  - 复杂任务可交由子agent去完成,和验收,你统领全局
11
12
  - 回复干净简洁,策划阶段不输出代码,具体执行再输出代码
12
13
  - 可以使用各种skill来协助,子智能体你可以在编排提示词阶段之间传入skill
14
+ - 动手改任何代码之前必须先向用户询问并确认,改代码过程中如果出现不确定的也要问
@@ -19,6 +19,15 @@ export interface PersonaValidationResult {
19
19
  }
20
20
  export declare function getGlobalPersonaDir(configHome?: string): string;
21
21
  export declare const DEFAULT_PERSONA_DIR: string;
22
+ /**
23
+ * 确保内置 prompt 目录同步到 ~/.agent/prompts/{name}/。
24
+ *
25
+ * 和 persona 不同(有 bootstrap 状态机),这只是一个简单的文件复制:
26
+ * 首次安装时复制,已存在则跳过。
27
+ *
28
+ * 用于 attention.md 等非 persona 的内置 prompt 文件。
29
+ */
30
+ export declare function ensureGlobalPromptDir(name: string, configHome?: string): Promise<string[]>;
22
31
  export declare function ensurePersonaFiles(personaDir: string): Promise<{
23
32
  status: BootstrapStatus;
24
33
  filesCreated: string[];
@@ -93,6 +93,58 @@ async function fileContentDiffersFromTemplate(filePath, template) {
93
93
  throw err;
94
94
  }
95
95
  }
96
+ // ─── Generic Prompt Sync ──────────────────────────────────────────────
97
+ /**
98
+ * 确保内置 prompt 目录同步到 ~/.agent/prompts/{name}/。
99
+ *
100
+ * 和 persona 不同(有 bootstrap 状态机),这只是一个简单的文件复制:
101
+ * 首次安装时复制,已存在则跳过。
102
+ *
103
+ * 用于 attention.md 等非 persona 的内置 prompt 文件。
104
+ */
105
+ export async function ensureGlobalPromptDir(name, configHome = path.join(os.homedir(), '.agent')) {
106
+ const targetDir = path.join(configHome, 'prompts', name);
107
+ await fsp.mkdir(targetDir, { recursive: true });
108
+ // 搜索源目录(与 loadTemplateContent 的搜索策略一致)
109
+ const sourceDirs = [
110
+ path.join(__dirname, '..', 'prompts', name),
111
+ path.join(process.cwd(), 'src', 'prompts', name),
112
+ path.join(process.cwd(), 'dist', 'prompts', name),
113
+ ];
114
+ let sourceDir;
115
+ for (const dir of sourceDirs) {
116
+ try {
117
+ const stat = await fsp.stat(dir);
118
+ if (stat.isDirectory()) {
119
+ sourceDir = dir;
120
+ break;
121
+ }
122
+ }
123
+ catch {
124
+ continue;
125
+ }
126
+ }
127
+ if (!sourceDir) {
128
+ logger.warn(`Source prompt directory not found: prompts/${name}. Searched: ${sourceDirs.join(', ')}`);
129
+ return [];
130
+ }
131
+ const filesCreated = [];
132
+ const entries = await fsp.readdir(sourceDir, { withFileTypes: true });
133
+ for (const entry of entries) {
134
+ if (!entry.isFile() || !entry.name.endsWith('.md'))
135
+ continue;
136
+ const sourcePath = path.join(sourceDir, entry.name);
137
+ const targetPath = path.join(targetDir, entry.name);
138
+ const created = await writeFileIfMissing(targetPath, await fsp.readFile(sourcePath, 'utf-8'));
139
+ if (created) {
140
+ filesCreated.push(`${name}/${entry.name}`);
141
+ }
142
+ }
143
+ if (filesCreated.length > 0) {
144
+ logger.info('Synced built-in prompt files', { dir: name, files: filesCreated });
145
+ }
146
+ return filesCreated;
147
+ }
96
148
  // ─── Public API ──────────────────────────────────────────────────────
97
149
  export async function ensurePersonaFiles(personaDir) {
98
150
  await fsp.mkdir(personaDir, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyacinth-ai",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "Hyacinth 🪻 — multi-provider AI agent with tool system and context management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,13 +29,11 @@
29
29
  "@modelcontextprotocol/sdk": "^1.29.0",
30
30
  "chalk": "^5.6.2",
31
31
  "chokidar": "^5.0.0",
32
- "chrome-devtools-mcp": "^1.2.0",
33
32
  "commander": "^14.0.3",
34
33
  "diff": "^9.0.0",
35
34
  "fastify": "^5.8.5",
36
35
  "js-tiktoken": "^1.0.21",
37
36
  "mammoth": "^1.12.0",
38
- "mcp-chrome-bridge": "^1.0.31",
39
37
  "openai": "^6.39.0",
40
38
  "picocolors": "^1.1.1",
41
39
  "sharp": "^0.35.0",
@@ -43,7 +41,9 @@
43
41
  "xlsx": "^0.18.5"
44
42
  },
45
43
  "optionalDependencies": {
46
- "better-sqlite3": "^12.10.0"
44
+ "better-sqlite3": "^12.10.0",
45
+ "chrome-devtools-mcp": "^1.2.0",
46
+ "mcp-chrome-bridge": "^1.0.31"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/better-sqlite3": "^7.6.13",