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.
- package/dist/channels/builtin/http-webhook.js +32 -0
- package/dist/context/section-resolver.js +1 -1
- package/dist/gateway/factory.js +12 -1
- package/dist/knowledge/structured-store.js +2 -2
- package/dist/prompts/attention/attention.md +2 -0
- package/dist/setup/persona-bootstrap.d.ts +9 -0
- package/dist/setup/persona-bootstrap.js +52 -0
- package/package.json +4 -4
|
@@ -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\
|
|
137
|
+
return `# currentDate\n(系统提供) Today is ${dateSlash}, ${timePart}.`;
|
|
138
138
|
}
|
|
139
139
|
if (src === 'runtime:userInput') {
|
|
140
140
|
return ctx.userInput || undefined;
|
package/dist/gateway/factory.js
CHANGED
|
@@ -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 (
|
|
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(
|
|
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.
|
|
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",
|