ym-hermes-desktop 1.0.2 → 1.0.4

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 CHANGED
@@ -42,6 +42,8 @@ npm run ask "帮我查一下今天杭州天气" # 单次提问
42
42
 
43
43
  ## 异步下发(下发即确认,完成后自动上报)
44
44
 
45
+ `hermes_chat` MCP 工具与库函数 `dispatch` 均为异步模式:**调用后立即返回「任务名 已下达」**,任务后台执行;完成后自动上报到 `REPORT_URL`(接口约定:raw JSON,上报体 `{ title(标题), content(提纯汇报内容), datetime(完成后+5秒, YYYY-MM-DD HH:mm:ss) }`)。
46
+
45
47
  ```js
46
48
  import { dispatch } from './src/hermes.js';
47
49
 
@@ -49,12 +51,20 @@ import { dispatch } from './src/hermes.js';
49
51
  const ack = dispatch({
50
52
  taskName: '周报生成',
51
53
  content: '请总结今天的工作生成周报',
52
- reportUrl: 'http://你的接口/report', // 可选;不传则用 .env 的 REPORT_URL
54
+ reportUrl: 'http://127.0.0.1:3780/api/mcp/yunxiangji', // 可选;不传则用 .env 的 REPORT_URL
53
55
  });
54
- // ack = { task_name: '周报生成', status: '已下达', received_at: '2026-09-12 22:52:21' }
56
+ // ack = { task_name: '周报生成', status: '已下达', received_at: '2026-09-13 05:38:48' }
55
57
  ```
56
58
 
57
- 后台执行流程:异步调 Hermes 完成任务 → 组装 `{ task_name, summary, executed_at(YYYY-MM-DD HH:mm:ss), duration_seconds }` → POST 到 `reportUrl`(或 `.env` 的 `REPORT_URL`)。
59
+ 上报 JSON 示例:
60
+
61
+ ```json
62
+ {
63
+ "title": "周报生成",
64
+ "content": "今天完成了……",
65
+ "datetime": "2026-09-13 05:38:54"
66
+ }
67
+ ```
58
68
 
59
69
  配合独立上报工具 `ym-hermes-task`(注册进 Hermes 的 MCP 工具 `report_task`)可双保险:客户端上报 + Hermes 任务结束主动上报。
60
70
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ym-hermes-desktop",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Hermes Agent 接入点客户端与 MCP server:把 Hermes(含其 MCP 工具)通过 OpenAI 兼容 API / MCP stdio 协议暴露给任意 AI 工具。",
5
5
  "type": "module",
6
6
  "main": "./src/hermes.js",
package/src/hermes.js CHANGED
@@ -71,6 +71,42 @@ function durationSeconds(ms) {
71
71
  return Math.round(ms / 10) / 100;
72
72
  }
73
73
 
74
+ /** 上报时间相对当前延后 5 秒(接口约定:完成后增加 5 秒) */
75
+ const REPORT_DELAY_MS = 5000;
76
+
77
+ /**
78
+ * 组装任务完成上报体(raw JSON):
79
+ * title 标题(任务名)
80
+ * content 提纯汇报内容(完成概要)
81
+ * datetime 完成后 +5 秒,格式 YYYY-MM-DD HH:mm:ss
82
+ */
83
+ export function buildReportPayload({ title, content } = {}, now = new Date()) {
84
+ return {
85
+ title: String(title ?? ''),
86
+ content: String(content ?? ''),
87
+ datetime: nowString(new Date(now.getTime() + REPORT_DELAY_MS)),
88
+ };
89
+ }
90
+
91
+ /**
92
+ * 上报任务完成到 REPORT_URL(.env 配置或参数传入)。
93
+ * @returns {{ok:boolean, skipped?:boolean, status?:number, payload:object}}
94
+ */
95
+ export async function reportTaskResult({ title, content, reportUrl } = {}) {
96
+ const url = reportUrl || loadEnv().reportUrl;
97
+ const payload = buildReportPayload({ title, content });
98
+ if (!url) return { ok: false, skipped: true, payload, reason: '未配置 REPORT_URL' };
99
+ const resp = await fetch(url, {
100
+ method: 'POST',
101
+ headers: { 'Content-Type': 'application/json' },
102
+ body: JSON.stringify(payload),
103
+ });
104
+ if (!resp.ok) {
105
+ throw new Error(`上报失败 HTTP ${resp.status}: ${(await resp.text()).slice(0, 300)}`);
106
+ }
107
+ return { ok: true, status: resp.status, payload };
108
+ }
109
+
74
110
  /**
75
111
  * 异步下发任务:立即返回「任务名 已下达」,后台执行 Hermes,完成后自动上报。
76
112
  *
@@ -93,32 +129,17 @@ export function dispatch({ taskName, content, reportUrl, onDone } = {}) {
93
129
  }
94
130
 
95
131
  async function runTaskAsync({ taskName, content, reportUrl, onDone }) {
96
- const startedMs = Date.now();
97
132
  const result = await chat({ messages: [{ role: 'user', content }] });
98
133
  const summary = result?.choices?.[0]?.message?.content || '(无文本结果)';
99
134
 
100
- const payload = {
101
- task_name: taskName,
102
- summary,
103
- executed_at: nowString(),
104
- duration_seconds: durationSeconds(Date.now() - startedMs),
105
- };
106
-
107
- const url = reportUrl || loadEnv().reportUrl;
108
- if (url) {
109
- const resp = await fetch(url, {
110
- method: 'POST',
111
- headers: { 'Content-Type': 'application/json' },
112
- body: JSON.stringify(payload),
113
- });
114
- if (!resp.ok) {
115
- throw new Error(`上报失败 HTTP ${resp.status}: ${(await resp.text()).slice(0, 300)}`);
116
- }
117
- console.log(`[dispatch:${taskName}] 完成并已上报 (HTTP ${resp.status})`);
118
- } else {
135
+ const r = await reportTaskResult({ title: taskName, content: summary, reportUrl });
136
+ if (r.skipped) {
119
137
  console.log(`[dispatch:${taskName}] 完成(未配置 REPORT_URL,跳过上报)`);
138
+ } else {
139
+ console.log(`[dispatch:${taskName}] 完成并已上报 (HTTP ${r.status})`);
120
140
  }
121
141
 
142
+ const payload = r.payload;
122
143
  if (onDone) onDone(payload);
123
144
  return payload;
124
145
  }
package/src/mcp-server.js CHANGED
@@ -2,19 +2,22 @@
2
2
  /**
3
3
  * mcp-server.js — 把 Hermes 能力暴露为 MCP server(stdio 传输)
4
4
  *
5
- * 任何 MCP 客户端(Cursor / Claude Desktop / Cherry Studio 等)连接本 server 后,
6
- * 可通过 hermes_chat 工具让 Hermes 执行任务(Hermes 会自行调度其注册的工具/MCP)。
5
+ * hermes_chat 工具为「异步下发」模式:
6
+ * 调用后立即返回「任务名 已下达」,后台执行 Hermes;任务完成后自动上报到
7
+ * REPORT_URL(接口约定:上报体 { executed_at(当前时间+8秒), summary })。
8
+ * 因此调用方不会因 Hermes 长任务而超时。
7
9
  *
8
10
  * 客户端配置:
9
11
  * {
10
12
  * "mcpServers": {
11
13
  * "ym-hermes": {
12
14
  * "command": "npx",
13
- * "args": ["-y", "ym-hermes-desktop", "ym-hermes-mcp"],
15
+ * "args": ["-y", "ym-hermes-desktop"],
14
16
  * "env": {
15
17
  * "HERMES_API_ENDPOINT": "http://127.0.0.1:8642/v1",
16
18
  * "HERMES_API_KEY": "你的 Bearer token",
17
- * "HERMES_MODEL": "hermes-agent"
19
+ * "HERMES_MODEL": "hermes-agent",
20
+ * "REPORT_URL": "http://127.0.0.1:3780/yunxiangji/CreateTask"
18
21
  * }
19
22
  * }
20
23
  * }
@@ -26,11 +29,11 @@ import {
26
29
  ListToolsRequestSchema,
27
30
  CallToolRequestSchema,
28
31
  } from '@modelcontextprotocol/sdk/types.js';
29
- import { chat } from './hermes.js';
32
+ import { chat, reportTaskResult, nowString } from './hermes.js';
30
33
  import { loadEnv } from './config.js';
31
34
 
32
35
  const server = new Server(
33
- { name: 'ym-hermes-desktop', version: '1.0.0' },
36
+ { name: 'ym-hermes-desktop', version: '1.0.2' },
34
37
  { capabilities: { tools: {} } }
35
38
  );
36
39
 
@@ -38,7 +41,7 @@ const TOOLS = [
38
41
  {
39
42
  name: 'hermes_chat',
40
43
  description:
41
- ' Hermes Agent 发送对话并获取回复。Hermes 会自动执行它已注册的工具(含 MCP 工具)来完成你的请求。',
44
+ '下发任务给 Hermes Agent:调用后立即返回「任务名 已下达」,任务在后台异步执行(Hermes 会自行调度它已注册的 MCP 工具);任务完成后自动上报到 REPORT_URL(上报内容:executed_at 当前时间延后8秒 + summary)。',
42
45
  inputSchema: {
43
46
  type: 'object',
44
47
  properties: {
@@ -57,6 +60,10 @@ const TOOLS = [
57
60
  },
58
61
  },
59
62
  },
63
+ task_name: {
64
+ type: 'string',
65
+ description: '可选,任务名称;不传则取 question 前 20 字',
66
+ },
60
67
  },
61
68
  },
62
69
  },
@@ -64,6 +71,24 @@ const TOOLS = [
64
71
 
65
72
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
66
73
 
74
+ /** 后台执行 Hermes 任务,完成后自动上报(不阻塞调用方) */
75
+ function runTaskInBackground(taskName, messages) {
76
+ (async () => {
77
+ try {
78
+ const result = await chat({ messages });
79
+ const summary = result?.choices?.[0]?.message?.content || '(无文本结果)';
80
+ const r = await reportTaskResult({ title: taskName, content: summary });
81
+ process.stderr.write(
82
+ r.skipped
83
+ ? `[ym-hermes] 任务「${taskName}」完成(未配置 REPORT_URL,未上报)\n`
84
+ : `[ym-hermes] 任务「${taskName}」完成并已上报 HTTP ${r.status}(${r.payload.executed_at})\n`
85
+ );
86
+ } catch (err) {
87
+ process.stderr.write(`[ym-hermes] 任务「${taskName}」失败: ${err.message}\n`);
88
+ }
89
+ })();
90
+ }
91
+
67
92
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
68
93
  const { name, arguments: args } = request.params;
69
94
  if (name !== 'hermes_chat') {
@@ -74,10 +99,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
74
99
  if (!messages) {
75
100
  throw new Error('缺少参数:请提供 question 或 messages');
76
101
  }
77
- const result = await chat({ messages });
78
- const content =
79
- result?.choices?.[0]?.message?.content ?? JSON.stringify(result ?? '');
80
- return { content: [{ type: 'text', text: String(content) }] };
102
+ const taskName =
103
+ String(args.task_name || '').trim() ||
104
+ String(args.question || 'Hermes 任务').slice(0, 20);
105
+
106
+ // 立即返回确认,后台异步执行
107
+ runTaskInBackground(taskName, messages);
108
+ return {
109
+ content: [
110
+ { type: 'text', text: `「${taskName}」已下达(${nowString()}),后台执行中,完成后自动上报` },
111
+ ],
112
+ };
81
113
  });
82
114
 
83
115
  // 启动前给出接入点检查提示(缺失配置时输出到 stderr,不干扰 MCP 协议)