weflow-cli 1.7.0 → 1.8.0
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/ARCHITECTURE.md +3 -2
- package/CHANGELOG.md +1949 -1
- package/OPERATIONS.md +774 -1
- package/README.en.md +93 -7
- package/README.md +31 -6
- package/bin/weflow-cli.ts +934 -27
- package/dist/bin/weflow-cli.js +952 -27
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/mcp-server/index.js +39 -13
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/core/appMsgFormat.d.ts +37 -0
- package/dist/src/core/appMsgFormat.d.ts.map +1 -0
- package/dist/src/core/appMsgFormat.js +106 -0
- package/dist/src/core/appMsgFormat.js.map +1 -0
- package/dist/src/core/ntCore.d.ts +19 -2
- package/dist/src/core/ntCore.d.ts.map +1 -1
- package/dist/src/core/ntCore.js +11 -1
- package/dist/src/core/ntCore.js.map +1 -1
- package/dist/src/core/sqlcipherCore.d.ts +0 -12
- package/dist/src/core/sqlcipherCore.d.ts.map +1 -1
- package/dist/src/core/sqlcipherCore.js +5 -64
- package/dist/src/core/sqlcipherCore.js.map +1 -1
- package/dist/src/core/wcdbCore.d.ts +1 -1
- package/dist/src/core/wcdbCore.d.ts.map +1 -1
- package/dist/src/core/wcdbCore.js +5 -1
- package/dist/src/core/wcdbCore.js.map +1 -1
- package/dist/src/panel/client.d.ts +25 -0
- package/dist/src/panel/client.d.ts.map +1 -0
- package/dist/src/panel/client.js +34 -0
- package/dist/src/panel/client.js.map +1 -0
- package/dist/src/panel/endpoint.d.ts +45 -0
- package/dist/src/panel/endpoint.d.ts.map +1 -0
- package/dist/src/panel/endpoint.js +132 -0
- package/dist/src/panel/endpoint.js.map +1 -0
- package/dist/src/panel/launch.d.ts +50 -0
- package/dist/src/panel/launch.d.ts.map +1 -0
- package/dist/src/panel/launch.js +74 -0
- package/dist/src/panel/launch.js.map +1 -0
- package/dist/src/panel/server.d.ts +23 -0
- package/dist/src/panel/server.d.ts.map +1 -0
- package/dist/src/panel/server.js +413 -0
- package/dist/src/panel/server.js.map +1 -0
- package/dist/src/panel/userId.d.ts +37 -0
- package/dist/src/panel/userId.d.ts.map +1 -0
- package/dist/src/panel/userId.js +56 -0
- package/dist/src/panel/userId.js.map +1 -0
- package/dist/src/services/assistantDaemon.d.ts +20 -3
- package/dist/src/services/assistantDaemon.d.ts.map +1 -1
- package/dist/src/services/assistantDaemon.js +46 -9
- package/dist/src/services/assistantDaemon.js.map +1 -1
- package/dist/src/services/assistantEval.d.ts +141 -0
- package/dist/src/services/assistantEval.d.ts.map +1 -0
- package/dist/src/services/assistantEval.js +497 -0
- package/dist/src/services/assistantEval.js.map +1 -0
- package/dist/src/services/assistantMemory.d.ts +106 -4
- package/dist/src/services/assistantMemory.d.ts.map +1 -1
- package/dist/src/services/assistantMemory.js +396 -61
- package/dist/src/services/assistantMemory.js.map +1 -1
- package/dist/src/services/assistantPrivacy.d.ts +15 -2
- package/dist/src/services/assistantPrivacy.d.ts.map +1 -1
- package/dist/src/services/assistantPrivacy.js +18 -4
- package/dist/src/services/assistantPrivacy.js.map +1 -1
- package/dist/src/services/assistantRouter.d.ts +41 -0
- package/dist/src/services/assistantRouter.d.ts.map +1 -0
- package/dist/src/services/assistantRouter.js +151 -0
- package/dist/src/services/assistantRouter.js.map +1 -0
- package/dist/src/services/assistantService.d.ts +209 -3
- package/dist/src/services/assistantService.d.ts.map +1 -1
- package/dist/src/services/assistantService.js +590 -75
- package/dist/src/services/assistantService.js.map +1 -1
- package/dist/src/services/assistantTools.d.ts +205 -1
- package/dist/src/services/assistantTools.d.ts.map +1 -1
- package/dist/src/services/assistantTools.js +1089 -58
- package/dist/src/services/assistantTools.js.map +1 -1
- package/dist/src/services/assistantTrace.d.ts +47 -0
- package/dist/src/services/assistantTrace.d.ts.map +1 -0
- package/dist/src/services/assistantTrace.js +150 -0
- package/dist/src/services/assistantTrace.js.map +1 -0
- package/dist/src/services/chatService.d.ts +1 -1
- package/dist/src/services/chatService.d.ts.map +1 -1
- package/dist/src/services/chatService.js +5 -2
- package/dist/src/services/chatService.js.map +1 -1
- package/dist/src/services/configService.d.ts +9 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +15 -8
- package/dist/src/services/configService.js.map +1 -1
- package/dist/src/services/pythonBridge.d.ts +41 -0
- package/dist/src/services/pythonBridge.d.ts.map +1 -0
- package/dist/src/services/pythonBridge.js +119 -0
- package/dist/src/services/pythonBridge.js.map +1 -0
- package/dist/src/services/syncService.d.ts +8 -2
- package/dist/src/services/syncService.d.ts.map +1 -1
- package/dist/src/services/syncService.js +26 -7
- package/dist/src/services/syncService.js.map +1 -1
- package/dist/src/services/wechatMessageService.d.ts +25 -0
- package/dist/src/services/wechatMessageService.d.ts.map +1 -1
- package/dist/src/services/wechatMessageService.js +69 -7
- package/dist/src/services/wechatMessageService.js.map +1 -1
- package/dist/src/utils/atomicWrite.d.ts +4 -0
- package/dist/src/utils/atomicWrite.d.ts.map +1 -0
- package/dist/src/utils/atomicWrite.js +60 -0
- package/dist/src/utils/atomicWrite.js.map +1 -0
- package/dist/src/utils/dateRange.d.ts +15 -0
- package/dist/src/utils/dateRange.d.ts.map +1 -1
- package/dist/src/utils/dateRange.js +47 -0
- package/dist/src/utils/dateRange.js.map +1 -1
- package/dist/src/utils/text.d.ts +18 -0
- package/dist/src/utils/text.d.ts.map +1 -0
- package/dist/src/utils/text.js +20 -0
- package/dist/src/utils/text.js.map +1 -0
- package/docs/AI_INTERFACE.md +2 -0
- package/docs/DECISIONS.md +1424 -0
- package/docs/EXTENDING.md +187 -0
- package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +310 -0
- package/docs/MCP.md +23 -4
- package/docs/PROJECT_STATE.md +299 -98
- package/docs/SETUP.md +22 -0
- package/docs/SYNC_CONTRACT.md +31 -3
- package/mcp-server/index.ts +681 -657
- package/package.json +84 -83
- package/resources/panel/ball-position.cjs +153 -0
- package/resources/panel/index.html +46 -0
- package/resources/panel/main.cjs +447 -0
- package/resources/panel/mascot-focus.png +0 -0
- package/resources/panel/mascot-happy.png +0 -0
- package/resources/panel/mascot-sorry.png +0 -0
- package/resources/panel/mascot-tired.png +0 -0
- package/resources/panel/mascot.png +0 -0
- package/resources/panel/package.json +7 -0
- package/resources/panel/panel.css +407 -0
- package/resources/panel/preload.cjs +72 -0
- package/resources/panel/quick-menu.cjs +197 -0
- package/resources/panel/renderer.js +441 -0
- package/resources/panel/tray-menu.cjs +40 -0
- package/resources/panel/tray.png +0 -0
- package/scripts/_utils.py +403 -54
- package/scripts/annual_report.py +4 -2
- package/scripts/article_notes.py +423 -0
- package/scripts/backfill_articles.py +438 -0
- package/scripts/biz_daily.py +590 -105
- package/scripts/chat_notes.py +470 -0
- package/scripts/classify_daily.py +10 -18
- package/scripts/compile_wiki.py +1059 -46
- package/scripts/create_reading_notes.py +199 -14
- package/scripts/decide.py +253 -0
- package/scripts/draft_reply.py +671 -0
- package/scripts/export_chat_html.py +35 -33
- package/scripts/extract_todos.py +31 -5
- package/scripts/fav_notes.py +318 -0
- package/scripts/fix_topics.py +2 -1
- package/scripts/generate_ai_report.py +214 -9
- package/scripts/generate_html.py +18 -3
- package/scripts/generate_review.py +2 -2
- package/scripts/jev_client.py +294 -0
- package/scripts/jev_probe.py +440 -0
- package/scripts/nt_common.py +82 -0
- package/scripts/nt_decrypt.py +1755 -1460
- package/scripts/pipeline.py +27 -2
- package/scripts/promote_ideas.py +19 -4
- package/scripts/quality_eval.py +707 -0
- package/scripts/rag_chat.py +4 -4
- package/scripts/read_image.py +187 -0
- package/scripts/reply_debt.py +488 -0
- package/scripts/route_cards.py +453 -0
- package/scripts/semantic_search.py +208 -49
- package/scripts/user_notes.py +315 -0
- package/scripts/vault_rag.py +13 -7
- package/scripts/vault_search.py +21 -4
- package/scripts/wiki_lint.py +397 -0
- package/src/core/appMsgFormat.ts +117 -0
- package/src/core/ntCore.ts +27 -2
- package/src/core/sqlcipherCore.ts +5 -61
- package/src/core/wcdbCore.ts +6 -2
- package/src/panel/client.ts +49 -0
- package/src/panel/endpoint.ts +144 -0
- package/src/panel/launch.ts +101 -0
- package/src/panel/server.ts +424 -0
- package/src/panel/userId.ts +72 -0
- package/src/services/assistantDaemon.ts +57 -9
- package/src/services/assistantEval.ts +611 -0
- package/src/services/assistantMemory.ts +413 -57
- package/src/services/assistantPrivacy.ts +18 -4
- package/src/services/assistantRouter.ts +191 -0
- package/src/services/assistantService.ts +671 -74
- package/src/services/assistantTools.ts +1118 -57
- package/src/services/assistantTrace.ts +175 -0
- package/src/services/chatService.ts +6 -2
- package/src/services/configService.ts +403 -385
- package/src/services/pythonBridge.ts +139 -0
- package/src/services/syncService.ts +37 -9
- package/src/services/wechatMessageService.ts +73 -7
- package/src/utils/atomicWrite.ts +56 -0
- package/src/utils/dateRange.ts +46 -0
- package/src/utils/text.ts +20 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 助手守护进程的本机入口:**只绑回环**的 HTTP 端点,给悬浮窗(和 `weflow-cli panel`)用。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有它:助手的配额计数器、串行队列、被那条队列保护的实例字段都是**进程内状态**,
|
|
5
|
+
* 所以"两个入口"必须落在**一个宿主进程**里。悬浮窗自己 `new AssistantService()` 会变成
|
|
6
|
+
* 第二个大脑:配额各算各的、两进程并发写同一个 userId 的记忆(`save()` 虽是合并写,
|
|
7
|
+
* 但同一个 userId 仍是后写覆盖先写)。**面板只做客户端。**
|
|
8
|
+
*
|
|
9
|
+
* ## 信任边界(D-045)
|
|
10
|
+
*
|
|
11
|
+
* 这条端点能读到用户的聊天数据,所以照着 `docs/DECISIONS.md` 的 D-004(阅读器只绑回环)
|
|
12
|
+
* 往下走,但**必须比它严**:
|
|
13
|
+
*
|
|
14
|
+
* - **绑 `127.0.0.1`,不可配置**。不是 LAN 服务。
|
|
15
|
+
* - **每个请求都要 token**,`GET /api/status` 也不例外。阅读器(`scripts/fav_server.py`)
|
|
16
|
+
* 根本没有 token,而且它的 Origin 校验**不带 Origin 就放行**——那是给"本机阅读器"的设计,
|
|
17
|
+
* 不能照抄到这个能替用户读聊天记录的端点上来。
|
|
18
|
+
* - **Origin 是第二道**:带了 Origin 但不在白名单 → 403,**即使 token 正确**。
|
|
19
|
+
* 反过来,**不带** Origin 要放行:`file://` 加载的 Electron 渲染进程与 `curl` 都是这种形态,
|
|
20
|
+
* 硬要求 Origin 会把面板自己挡在门外。
|
|
21
|
+
* - **POST 必须 `Content-Type: application/json`**:挡掉"跨站简单请求"那种不发 preflight 的形态。
|
|
22
|
+
* - **同时只允许一轮在飞**:单轮最坏是分钟级(6 轮工具 + 守卫一轮 + 压缩/抽取各一次 LLM),
|
|
23
|
+
* 用户在转圈时连按回车会往队列里挂一串——每一个都烧配额。第 2 个直接 429,不入队。
|
|
24
|
+
*
|
|
25
|
+
* 与助手出站那个 `isSafeUrl()`(`assistantTools.ts`)**不是一回事**:它管的是"助手去抓网页",
|
|
26
|
+
* 回答的是另一个问题(那边**拒绝**回环地址)。别把两者"统一"起来。
|
|
27
|
+
*/
|
|
28
|
+
import { createServer } from 'node:http';
|
|
29
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
30
|
+
import { join } from 'node:path';
|
|
31
|
+
import { randomBytes } from 'node:crypto';
|
|
32
|
+
import { DEFAULT_PANEL_PORT, PANEL_ENDPOINT_VERSION, PANEL_SERVICE_ID, generateToken, tokenMatches, writeEndpoint, } from './endpoint.js';
|
|
33
|
+
import { privacyGate } from '../services/assistantPrivacy.js';
|
|
34
|
+
import { configService } from '../services/configService.js';
|
|
35
|
+
import { resolvePackageRoot } from '../utils/packageRoot.js';
|
|
36
|
+
/** 8KB 够一句问题用;超了就是有人在灌东西 */
|
|
37
|
+
const MAX_BODY_BYTES = 8 * 1024;
|
|
38
|
+
/** 一轮最坏分钟级(见上),给足余量;客户端那边也有自己的超时 */
|
|
39
|
+
const TURN_TIMEOUT_MS = 180_000;
|
|
40
|
+
/** 浏览器那条路的会话 cookie 名。值是 token 本身,但 `HttpOnly` —— 页面脚本拿不到它 */
|
|
41
|
+
const COOKIE_NAME = 'weflow_panel';
|
|
42
|
+
/** 一次性口令的有效期。只用来"换 cookie"这一次,换个页面就该过期了 */
|
|
43
|
+
const PAIR_TTL_MS = 60_000;
|
|
44
|
+
/**
|
|
45
|
+
* 静态文件**白名单**。不是"把目录挂出去":只认这三个名字,其余一律 404。
|
|
46
|
+
* 挂目录就得处理路径穿越,而这里根本不需要那个能力。
|
|
47
|
+
*/
|
|
48
|
+
const STATIC_FILES = {
|
|
49
|
+
'/panel': 'index.html',
|
|
50
|
+
'/panel/': 'index.html',
|
|
51
|
+
'/panel/index.html': 'index.html',
|
|
52
|
+
'/panel/renderer.js': 'renderer.js',
|
|
53
|
+
'/panel/panel.css': 'panel.css',
|
|
54
|
+
// 悬浮球上那张图(项目吉祥物)。走白名单而不是挂目录,理由同上。
|
|
55
|
+
'/panel/mascot.png': 'mascot.png',
|
|
56
|
+
// 被捏一下时换的那张(同一只猫,眯眼笑)。**必须一起进白名单**——漏了它,
|
|
57
|
+
// 球在按下的一瞬间会闪成一张空图(`PANEL_ASSET_MISSING` 只写在响应里,页面上看不到)。
|
|
58
|
+
'/panel/mascot-happy.png': 'mascot-happy.png',
|
|
59
|
+
// 三个真状态的脸(忙/连不上/额度用完)。与上面那张一样,**必须一起进白名单**——
|
|
60
|
+
// 漏一张,那个状态一出现球就闪成空图,而响应里的 PANEL_ASSET_MISSING 页面上看不到。
|
|
61
|
+
'/panel/mascot-focus.png': 'mascot-focus.png',
|
|
62
|
+
'/panel/mascot-sorry.png': 'mascot-sorry.png',
|
|
63
|
+
'/panel/mascot-tired.png': 'mascot-tired.png',
|
|
64
|
+
};
|
|
65
|
+
const CONTENT_TYPES = {
|
|
66
|
+
'.html': 'text/html; charset=utf-8',
|
|
67
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
68
|
+
'.css': 'text/css; charset=utf-8',
|
|
69
|
+
'.png': 'image/png',
|
|
70
|
+
};
|
|
71
|
+
function json(res, status, payload) {
|
|
72
|
+
const body = JSON.stringify(payload);
|
|
73
|
+
res.writeHead(status, {
|
|
74
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
75
|
+
'Cache-Control': 'no-store',
|
|
76
|
+
'X-Content-Type-Options': 'nosniff',
|
|
77
|
+
});
|
|
78
|
+
res.end(body);
|
|
79
|
+
}
|
|
80
|
+
/** 只认本机来源。带 Origin 就必须对得上;不带 Origin 放行(curl / file:// 的 Electron) */
|
|
81
|
+
export function isLocalOrigin(origin, port) {
|
|
82
|
+
if (!origin)
|
|
83
|
+
return true;
|
|
84
|
+
return origin === `http://127.0.0.1:${port}` || origin === `http://localhost:${port}`;
|
|
85
|
+
}
|
|
86
|
+
function bearer(req) {
|
|
87
|
+
const header = req.headers['authorization'];
|
|
88
|
+
if (typeof header !== 'string')
|
|
89
|
+
return undefined;
|
|
90
|
+
const match = /^Bearer\s+(.+)$/i.exec(header.trim());
|
|
91
|
+
return match ? match[1] : undefined;
|
|
92
|
+
}
|
|
93
|
+
function cookieToken(req) {
|
|
94
|
+
const raw = req.headers['cookie'];
|
|
95
|
+
if (typeof raw !== 'string')
|
|
96
|
+
return undefined;
|
|
97
|
+
for (const part of raw.split(';')) {
|
|
98
|
+
const [name, ...rest] = part.trim().split('=');
|
|
99
|
+
if (name === COOKIE_NAME)
|
|
100
|
+
return rest.join('=');
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 这一份请求是拿什么来证明自己的:CLI/Python 走 `Authorization`,**浏览器走 cookie**。
|
|
106
|
+
*
|
|
107
|
+
* 浏览器为什么不能也用 header:页面脚本一旦拿得到 token,就等于把 token 交给了渲染进程,
|
|
108
|
+
* 而渲染进程要显示的是**助手回复**——那里面有用户的聊天内容。用 `HttpOnly` cookie,
|
|
109
|
+
* 脚本就永远读不到它(防的是 XSS 拿 token 去干别的事),同时页面照常能发请求。
|
|
110
|
+
*/
|
|
111
|
+
function presentedToken(req) {
|
|
112
|
+
return bearer(req) ?? cookieToken(req);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 读请求体,超限就**立刻**给结论,但**不掐连接**。
|
|
116
|
+
*
|
|
117
|
+
* 第一版在这里 `req.destroy()` 了:结果是客户端看到"连接被重置"而不是 413
|
|
118
|
+
* ——`fetch` 直接抛 `fetch failed`,调用方拿不到那个状态码。所以超限之后**丢弃余下的字节**
|
|
119
|
+
* 而不是断开(回环端点上的这点带宽代价,比"给不出 413"划算得多)。
|
|
120
|
+
*/
|
|
121
|
+
function readBody(req) {
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
let size = 0;
|
|
124
|
+
let tooLarge = false;
|
|
125
|
+
const chunks = [];
|
|
126
|
+
req.on('data', (chunk) => {
|
|
127
|
+
if (tooLarge)
|
|
128
|
+
return; // 已经下过结论了,余下的丢掉
|
|
129
|
+
size += chunk.length;
|
|
130
|
+
if (size > MAX_BODY_BYTES) {
|
|
131
|
+
tooLarge = true;
|
|
132
|
+
resolve({ ok: false, code: 'PAYLOAD_TOO_LARGE' });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
chunks.push(chunk);
|
|
136
|
+
});
|
|
137
|
+
req.on('end', () => { if (!tooLarge)
|
|
138
|
+
resolve({ ok: true, text: Buffer.concat(chunks).toString('utf8') }); });
|
|
139
|
+
req.on('error', () => resolve({ ok: false, code: 'BODY_READ_FAILED' }));
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 端口的环境变量覆盖。**只给测试用**(`WEFLOW_PANEL_PORT=0` 让内核分配一个临时端口),
|
|
144
|
+
* 与 `WEFLOW_ASSISTANT_EXPORT_ROOT` 是同一个先例。
|
|
145
|
+
*
|
|
146
|
+
* 为什么需要它:测试里 `AssistantService.start()` 会真的把这个端点起起来,
|
|
147
|
+
* 于是**每个用到 store 的测试都会去抢生产端口 8766**——真守护进程在跑时就是 EADDRINUSE,
|
|
148
|
+
* 而且测试之间也会互相抢。非法值一律当没给(不猜、不静默用一个奇怪的端口)。
|
|
149
|
+
*/
|
|
150
|
+
function portFromEnv() {
|
|
151
|
+
const raw = process.env.WEFLOW_PANEL_PORT;
|
|
152
|
+
if (raw === undefined || raw === '')
|
|
153
|
+
return undefined;
|
|
154
|
+
const parsed = Number(raw);
|
|
155
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535)
|
|
156
|
+
return undefined;
|
|
157
|
+
return parsed;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 起端点。返回**实际**监听的端口——调用方要把它写进端点文件,不要用请求的那个值。
|
|
161
|
+
*/
|
|
162
|
+
export async function startPanelServer(options) {
|
|
163
|
+
const { service, memoryBucket, memoryNote = '', channel, onLog } = options;
|
|
164
|
+
const wantedPort = options.port ?? portFromEnv() ?? DEFAULT_PANEL_PORT;
|
|
165
|
+
const token = generateToken();
|
|
166
|
+
const startedAt = new Date().toISOString();
|
|
167
|
+
let inFlight = false;
|
|
168
|
+
const server = createServer((req, res) => {
|
|
169
|
+
void handle(req, res).catch(() => {
|
|
170
|
+
if (!res.headersSent)
|
|
171
|
+
json(res, 500, { ok: false, code: 'INTERNAL' });
|
|
172
|
+
else
|
|
173
|
+
res.end();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
/**
|
|
177
|
+
* 一次性口令 → cookie。**为什么要有这一步**:浏览器那条路(Edge `--app`)得先拿到凭据,
|
|
178
|
+
* 而**不能把 token 放进 URL**——命令行对同机任何进程可见(`wmic process get commandline`),
|
|
179
|
+
* 而且 URL 会进浏览器历史。所以给的是一个**一次性、60 秒、用完即废**的口令。
|
|
180
|
+
*/
|
|
181
|
+
const pairCodes = new Map();
|
|
182
|
+
function issuePairCode() {
|
|
183
|
+
const code = randomBytes(18).toString('base64url');
|
|
184
|
+
pairCodes.set(code, Date.now() + PAIR_TTL_MS);
|
|
185
|
+
// 顺手清掉过期的,别让这张表无限长
|
|
186
|
+
for (const [k, exp] of pairCodes)
|
|
187
|
+
if (exp < Date.now())
|
|
188
|
+
pairCodes.delete(k);
|
|
189
|
+
return code;
|
|
190
|
+
}
|
|
191
|
+
function consumePairCode(code) {
|
|
192
|
+
const exp = pairCodes.get(code);
|
|
193
|
+
if (!exp)
|
|
194
|
+
return false;
|
|
195
|
+
pairCodes.delete(code); // **单次使用**:即使它被日志/历史记下来,也已经废了
|
|
196
|
+
return exp >= Date.now();
|
|
197
|
+
}
|
|
198
|
+
function serveStatic(res, fileName) {
|
|
199
|
+
try {
|
|
200
|
+
const full = join(resolvePackageRoot(import.meta.url), 'resources', 'panel', fileName);
|
|
201
|
+
if (!existsSync(full)) {
|
|
202
|
+
json(res, 503, { ok: false, code: 'PANEL_ASSET_MISSING', file: fileName });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const ext = fileName.slice(fileName.lastIndexOf('.'));
|
|
206
|
+
res.writeHead(200, {
|
|
207
|
+
'Content-Type': CONTENT_TYPES[ext] ?? 'application/octet-stream',
|
|
208
|
+
'Cache-Control': 'no-store',
|
|
209
|
+
// CSP 走响应头而不是 <meta>:头不能被页面内容绕过
|
|
210
|
+
'Content-Security-Policy': "default-src 'none'; script-src 'self'; style-src 'self'; "
|
|
211
|
+
+ "connect-src 'self'; img-src 'self' data:; form-action 'none'; base-uri 'none'",
|
|
212
|
+
'X-Content-Type-Options': 'nosniff',
|
|
213
|
+
'Referrer-Policy': 'no-referrer',
|
|
214
|
+
});
|
|
215
|
+
res.end(readFileSync(full));
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
json(res, 500, { ok: false, code: 'INTERNAL' });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function handle(req, res) {
|
|
222
|
+
const port = server.address()?.port ?? wantedPort;
|
|
223
|
+
const url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);
|
|
224
|
+
if (!isLocalOrigin(req.headers.origin, port)) {
|
|
225
|
+
json(res, 403, { ok: false, code: 'ORIGIN_DENIED' });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// ---------------------------------------------------------- 页面(浏览器那条路)
|
|
229
|
+
const asset = STATIC_FILES[url.pathname];
|
|
230
|
+
if (asset) {
|
|
231
|
+
if (req.method !== 'GET') {
|
|
232
|
+
json(res, 405, { ok: false, code: 'METHOD_NOT_ALLOWED' });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const code = url.searchParams.get('c');
|
|
236
|
+
const authed = tokenMatches(token, presentedToken(req));
|
|
237
|
+
if (!authed) {
|
|
238
|
+
// 没带凭据:只接受一次性口令,并把 cookie 种上(token **不进 URL**,见 issuePairCode)
|
|
239
|
+
if (!code || !consumePairCode(code)) {
|
|
240
|
+
json(res, 401, { ok: false, code: 'UNAUTHORIZED', hint: '用 weflow-cli panel 打开' });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
res.setHeader('Set-Cookie', `${COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Path=/`);
|
|
244
|
+
}
|
|
245
|
+
// 只在**页面本身**加载时记一行。不记 `/api/status`:面板每 30 秒轮询一次状态,
|
|
246
|
+
// 记它会把日志灌满,而它想回答的问题("界面开着吗、按哪条路进来的")这一行就答了。
|
|
247
|
+
if (asset === 'index.html') {
|
|
248
|
+
onLog?.(`[panel] 界面已加载(${code && !authed ? '一次性口令' : 'cookie'})`);
|
|
249
|
+
}
|
|
250
|
+
serveStatic(res, asset);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (!tokenMatches(token, presentedToken(req))) {
|
|
254
|
+
// 不回显任何东西:错的 token 与缺的 token 得到完全一样的响应
|
|
255
|
+
json(res, 401, { ok: false, code: 'UNAUTHORIZED' });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (url.pathname === '/api/pair') {
|
|
259
|
+
if (req.method !== 'POST') {
|
|
260
|
+
json(res, 405, { ok: false, code: 'METHOD_NOT_ALLOWED' });
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
// 换口令这一步**必须带真实 token**(上面的检查已经过了),所以本机别的东西换不到
|
|
264
|
+
const code = issuePairCode();
|
|
265
|
+
json(res, 200, {
|
|
266
|
+
ok: true, code, expiresInMs: PAIR_TTL_MS,
|
|
267
|
+
url: `http://127.0.0.1:${port}/panel?c=${code}`,
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (url.pathname === '/api/status') {
|
|
272
|
+
if (req.method !== 'GET') {
|
|
273
|
+
json(res, 405, { ok: false, code: 'METHOD_NOT_ALLOWED' });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
json(res, 200, {
|
|
277
|
+
ok: true,
|
|
278
|
+
service: PANEL_SERVICE_ID,
|
|
279
|
+
startedAt,
|
|
280
|
+
channel,
|
|
281
|
+
channelActive: service.isChannelActive(),
|
|
282
|
+
memoryBucket,
|
|
283
|
+
memoryNote,
|
|
284
|
+
quota: service.quotaState(),
|
|
285
|
+
aiConfigured: privacyGate.isLocalInference() || !!configService.get('deepseekApiKey'),
|
|
286
|
+
// 右键"快速回复"的名单(`config set quickReplyContacts "甲,乙"`)。
|
|
287
|
+
// 页面本来就在轮询这个端点,所以它不必再开一个入口——**面板那条路上不加新端点**。
|
|
288
|
+
// 名字与 id 都在这里:菜单显示名字,`/api/ask` 收到的是名字(解析交给助手那一侧,
|
|
289
|
+
// 重名会得到"匹配多个会话"这种可读的失败,不猜)。
|
|
290
|
+
quickReplies: String(configService.get('quickReplyContacts') || '')
|
|
291
|
+
.split(',').map(item => item.trim()).filter(Boolean).slice(0, 20),
|
|
292
|
+
});
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (url.pathname === '/api/ask') {
|
|
296
|
+
if (req.method !== 'POST') {
|
|
297
|
+
json(res, 405, { ok: false, code: 'METHOD_NOT_ALLOWED' });
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const contentType = String(req.headers['content-type'] ?? '');
|
|
301
|
+
if (!contentType.toLowerCase().startsWith('application/json')) {
|
|
302
|
+
json(res, 415, { ok: false, code: 'UNSUPPORTED_MEDIA_TYPE' });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (inFlight) {
|
|
306
|
+
// 不入队:入队意味着它**一定会被执行**,也就一定会烧配额
|
|
307
|
+
json(res, 429, { ok: false, code: 'BUSY', error: '上一句还在处理' });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const body = await readBody(req);
|
|
311
|
+
if (!body.ok) {
|
|
312
|
+
json(res, body.code === 'PAYLOAD_TOO_LARGE' ? 413 : 400, { ok: false, code: body.code });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
let text = '';
|
|
316
|
+
try {
|
|
317
|
+
text = String(JSON.parse(body.text)?.text ?? '');
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
json(res, 400, { ok: false, code: 'BAD_JSON' });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
text = text.trim();
|
|
324
|
+
if (!text) {
|
|
325
|
+
json(res, 400, { ok: false, code: 'EMPTY_TEXT' });
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (text.length > 4000) {
|
|
329
|
+
json(res, 413, { ok: false, code: 'TEXT_TOO_LONG' });
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
inFlight = true;
|
|
333
|
+
try {
|
|
334
|
+
const raced = await withTimeout(service.ask(memoryBucket, text, onLog), TURN_TIMEOUT_MS);
|
|
335
|
+
if (raced.kind === 'timeout') {
|
|
336
|
+
// 超时**不等于**这一轮没跑完:它可能还在队列里跑着。所以这里只是不再等它,
|
|
337
|
+
// 那一轮之后仍会落记忆、仍会占配额。日志里说清,别让人以为它被取消了。
|
|
338
|
+
onLog?.(' → 本机入口不再等待这一轮(它可能仍在队列里跑)');
|
|
339
|
+
json(res, 504, { ok: false, code: 'TIMEOUT' });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (raced.kind === 'failed') {
|
|
343
|
+
onLog?.(` → 本机入口抛错: ${raced.message}`);
|
|
344
|
+
json(res, 500, { ok: false, code: 'TURN_FAILED' });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const outcome = raced.value;
|
|
348
|
+
if (outcome.status === 'replied' || outcome.status === 'quota-exceeded') {
|
|
349
|
+
json(res, 200, { ok: true, status: outcome.status, reply: outcome.text, memoryBucket });
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (outcome.status === 'not-running') {
|
|
353
|
+
json(res, 503, { ok: false, code: 'NOT_RUNNING' });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
// `denied` / `ignored` 对本机入口不该出现(它不过白名单)。真出现就是接线错了,
|
|
357
|
+
// 记在本机日志里,对客户端只说"这一轮没成",**不回显内部细节**。
|
|
358
|
+
onLog?.(` → 本机入口异常结果: ${outcome.status}`);
|
|
359
|
+
json(res, 500, { ok: false, code: 'TURN_FAILED' });
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
inFlight = false;
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
json(res, 404, { ok: false, code: 'NOT_FOUND' });
|
|
367
|
+
}
|
|
368
|
+
await new Promise((resolve, reject) => {
|
|
369
|
+
server.once('error', reject);
|
|
370
|
+
// **只绑回环**。这个字面量就是 D-004 那条决策的落点,不要做成可配置项。
|
|
371
|
+
server.listen(wantedPort, '127.0.0.1', () => {
|
|
372
|
+
server.removeListener('error', reject);
|
|
373
|
+
resolve();
|
|
374
|
+
});
|
|
375
|
+
}).catch((error) => {
|
|
376
|
+
if (error?.code === 'EADDRINUSE') {
|
|
377
|
+
throw new Error(`本机端点端口 ${wantedPort} 已被占用(换一个:--port,或先停掉占用的程序)`);
|
|
378
|
+
}
|
|
379
|
+
throw error;
|
|
380
|
+
});
|
|
381
|
+
const actualPort = server.address().port;
|
|
382
|
+
const endpoint = {
|
|
383
|
+
version: PANEL_ENDPOINT_VERSION,
|
|
384
|
+
service: PANEL_SERVICE_ID,
|
|
385
|
+
port: actualPort,
|
|
386
|
+
token,
|
|
387
|
+
pid: process.pid,
|
|
388
|
+
startedAt,
|
|
389
|
+
channel,
|
|
390
|
+
memoryBucket,
|
|
391
|
+
};
|
|
392
|
+
writeEndpoint(endpoint);
|
|
393
|
+
return {
|
|
394
|
+
port: actualPort,
|
|
395
|
+
endpoint,
|
|
396
|
+
close: () => new Promise((resolve) => {
|
|
397
|
+
server.close(() => resolve());
|
|
398
|
+
// 长连接挂着时 close 不会回调;本机服务没有长连接,但给个兜底
|
|
399
|
+
setTimeout(resolve, 500).unref?.();
|
|
400
|
+
}),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 等一轮,但不超过 `ms`。**超时与失败必须分开报**:把"抛错了"也说成"超时",
|
|
405
|
+
* 用户会去查网络,而真正的原因在日志里被掩掉了。
|
|
406
|
+
*/
|
|
407
|
+
function withTimeout(promise, ms) {
|
|
408
|
+
return new Promise((resolve) => {
|
|
409
|
+
const timer = setTimeout(() => resolve({ kind: 'timeout' }), ms);
|
|
410
|
+
promise.then((value) => { clearTimeout(timer); resolve({ kind: 'done', value }); }, (error) => { clearTimeout(timer); resolve({ kind: 'failed', message: String(error?.message ?? error) }); });
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/panel/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本机面板用哪个记忆桶 —— **这决定了它跟微信里那个是不是"同一个大脑"**。
|
|
3
|
+
*
|
|
4
|
+
* 记忆的事实是**按 userId 分**的(`assistant_memory.json` 的 `users["<会话id>"].facts`),
|
|
5
|
+
* 所以"共用记忆"不是一句口号,它等于"两边用同一个字符串当 userId"。
|
|
6
|
+
*
|
|
7
|
+
* 推理链是硬的:直聊时 `resolveInboundRouting` 给出的 `conversationId` 就是 `senderId`
|
|
8
|
+
* (`assistantRouting.ts:73`、`:78-82`),而白名单比的正是 `senderId`(`:31-35`)。
|
|
9
|
+
* **但这条推理在真实微信消息上从未被观测过**——本机那条通道从来没登录成功过
|
|
10
|
+
* (`docs/PROJECT_STATE.md`)。所以这里的态度是:
|
|
11
|
+
*
|
|
12
|
+
* - **恰好一条**白名单 → 就用它(那正是坐在这台机器前的人),并标记 `shared: true`;
|
|
13
|
+
* - **零条或两条以上 → 不猜**。返回一个 `needsAnswer` 的结果,让界面**问一次**
|
|
14
|
+
* 并把答案存进 `assistantPanelUser`,而不是静默落进一个跟微信隔离的桶里
|
|
15
|
+
* ——那样用户会以为在共用一个大脑,而实际上没有。
|
|
16
|
+
*
|
|
17
|
+
* 界面必须把解析出来的桶显示出来("当前记忆桶:xxx"),否则上面这些区分用户看不见。
|
|
18
|
+
*/
|
|
19
|
+
export interface PanelUserResolution {
|
|
20
|
+
/** 用哪个 userId 当记忆桶 */
|
|
21
|
+
userId: string;
|
|
22
|
+
/** 是不是与白名单里那个人共用(true 才是"一个大脑") */
|
|
23
|
+
shared: boolean;
|
|
24
|
+
/** 需要用户在界面上选一次(零条或两条以上且没有显式配置) */
|
|
25
|
+
needsAnswer: boolean;
|
|
26
|
+
/** 需要回答时列出候选(可能为空数组 = 白名单是空的) */
|
|
27
|
+
candidates: string[];
|
|
28
|
+
/** 给界面看的一句话,说明现在用的是哪个桶、为什么 */
|
|
29
|
+
note: string;
|
|
30
|
+
}
|
|
31
|
+
/** 与本机面板无关的兜底桶:白名单为空、用户又没指定时用它 */
|
|
32
|
+
export declare const PANEL_FALLBACK_BUCKET = "panel";
|
|
33
|
+
export declare function resolvePanelUserId(input: {
|
|
34
|
+
whitelist?: unknown;
|
|
35
|
+
configured?: unknown;
|
|
36
|
+
}): PanelUserResolution;
|
|
37
|
+
//# sourceMappingURL=userId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userId.d.ts","sourceRoot":"","sources":["../../../src/panel/userId.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,mCAAmC;IACnC,MAAM,EAAE,OAAO,CAAA;IACf,kCAAkC;IAClC,WAAW,EAAE,OAAO,CAAA;IACpB,iCAAiC;IACjC,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAA;CACb;AAED,kCAAkC;AAClC,eAAO,MAAM,qBAAqB,UAAU,CAAA;AAM5C,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,mBAAmB,CAgC5G"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本机面板用哪个记忆桶 —— **这决定了它跟微信里那个是不是"同一个大脑"**。
|
|
3
|
+
*
|
|
4
|
+
* 记忆的事实是**按 userId 分**的(`assistant_memory.json` 的 `users["<会话id>"].facts`),
|
|
5
|
+
* 所以"共用记忆"不是一句口号,它等于"两边用同一个字符串当 userId"。
|
|
6
|
+
*
|
|
7
|
+
* 推理链是硬的:直聊时 `resolveInboundRouting` 给出的 `conversationId` 就是 `senderId`
|
|
8
|
+
* (`assistantRouting.ts:73`、`:78-82`),而白名单比的正是 `senderId`(`:31-35`)。
|
|
9
|
+
* **但这条推理在真实微信消息上从未被观测过**——本机那条通道从来没登录成功过
|
|
10
|
+
* (`docs/PROJECT_STATE.md`)。所以这里的态度是:
|
|
11
|
+
*
|
|
12
|
+
* - **恰好一条**白名单 → 就用它(那正是坐在这台机器前的人),并标记 `shared: true`;
|
|
13
|
+
* - **零条或两条以上 → 不猜**。返回一个 `needsAnswer` 的结果,让界面**问一次**
|
|
14
|
+
* 并把答案存进 `assistantPanelUser`,而不是静默落进一个跟微信隔离的桶里
|
|
15
|
+
* ——那样用户会以为在共用一个大脑,而实际上没有。
|
|
16
|
+
*
|
|
17
|
+
* 界面必须把解析出来的桶显示出来("当前记忆桶:xxx"),否则上面这些区分用户看不见。
|
|
18
|
+
*/
|
|
19
|
+
/** 与本机面板无关的兜底桶:白名单为空、用户又没指定时用它 */
|
|
20
|
+
export const PANEL_FALLBACK_BUCKET = 'panel';
|
|
21
|
+
function parseList(value) {
|
|
22
|
+
return String(value ?? '').split(/[,;\s]+/).map(s => s.trim()).filter(Boolean);
|
|
23
|
+
}
|
|
24
|
+
export function resolvePanelUserId(input) {
|
|
25
|
+
const configured = String(input.configured ?? '').trim();
|
|
26
|
+
if (configured) {
|
|
27
|
+
// 显式配置**永远优先**,而且不再问:用户已经写过一次答案了
|
|
28
|
+
const inWhitelist = parseList(input.whitelist).includes(configured);
|
|
29
|
+
return {
|
|
30
|
+
userId: configured, shared: inWhitelist, needsAnswer: false, candidates: [],
|
|
31
|
+
note: inWhitelist
|
|
32
|
+
? `记忆桶:${configured}(与微信白名单里的同一个 id,共用一个大脑)`
|
|
33
|
+
: `记忆桶:${configured}(不在微信白名单里,与微信那边是分开的记忆)`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const ids = parseList(input.whitelist);
|
|
37
|
+
if (ids.length === 1) {
|
|
38
|
+
return {
|
|
39
|
+
userId: ids[0], shared: true, needsAnswer: false, candidates: ids,
|
|
40
|
+
note: `记忆桶:${ids[0]}(取自微信白名单里唯一的那个人,共用一个大脑)`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (ids.length === 0) {
|
|
44
|
+
return {
|
|
45
|
+
userId: PANEL_FALLBACK_BUCKET, shared: false, needsAnswer: true, candidates: [],
|
|
46
|
+
note: `微信白名单是空的,所以面板暂时用独立的「${PANEL_FALLBACK_BUCKET}」桶`
|
|
47
|
+
+ '——与微信那边是**分开的**记忆。选定一个身份即可共用。',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
userId: PANEL_FALLBACK_BUCKET, shared: false, needsAnswer: true, candidates: ids,
|
|
52
|
+
note: `微信白名单里有 ${ids.length} 个人,不确定这台机器前是谁,所以面板暂时用独立的`
|
|
53
|
+
+ `「${PANEL_FALLBACK_BUCKET}」桶。选定一个即可与那个人共用记忆。`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=userId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userId.js","sourceRoot":"","sources":["../../../src/panel/userId.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAeH,kCAAkC;AAClC,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAA;AAE5C,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AAChF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAoD;IACrF,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACxD,IAAI,UAAU,EAAE,CAAC;QACf,iCAAiC;QACjC,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QACnE,OAAO;YACL,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;YAC3E,IAAI,EAAE,WAAW;gBACf,CAAC,CAAC,OAAO,UAAU,yBAAyB;gBAC5C,CAAC,CAAC,OAAO,UAAU,wBAAwB;SAC9C,CAAA;IACH,CAAC;IAED,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACtC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG;YACjE,IAAI,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,yBAAyB;SAC7C,CAAA;IACH,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,MAAM,EAAE,qBAAqB,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE;YAC/E,IAAI,EAAE,uBAAuB,qBAAqB,IAAI;kBAClD,+BAA+B;SACpC,CAAA;IACH,CAAC;IACD,OAAO;QACL,MAAM,EAAE,qBAAqB,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG;QAChF,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,2BAA2B;cAClD,IAAI,qBAAqB,oBAAoB;KAClD,CAAA;AACH,CAAC"}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
export interface StartDaemonOptions {
|
|
3
|
+
/** 注入的 spawn,测试用;默认 child_process.spawn */
|
|
4
|
+
spawnImpl?: typeof spawn;
|
|
5
|
+
/** 存活观察窗口(ms),默认 700 */
|
|
6
|
+
settleMs?: number;
|
|
7
|
+
}
|
|
1
8
|
export declare function isDaemonAlive(): {
|
|
2
9
|
alive: boolean;
|
|
3
10
|
pid: number | null;
|
|
@@ -6,12 +13,22 @@ export declare function appendLog(line: string): void;
|
|
|
6
13
|
/** 日志轮转: 超过 1MB 保留尾部 256KB, 防止常驻进程日志无限增长 */
|
|
7
14
|
export declare function rotateLogIfNeeded(): void;
|
|
8
15
|
export declare function tailLog(n?: number): string;
|
|
9
|
-
/** 后台启动守护进程 (detached, 脱离终端生命周期)
|
|
10
|
-
|
|
16
|
+
/** 后台启动守护进程 (detached, 脱离终端生命周期)
|
|
17
|
+
*
|
|
18
|
+
* 子进程带 `--yes`:**确认发生在这一层**——人敲 `assistant start`,或机器用
|
|
19
|
+
* `--json --yes`——而 `assistant run` 自己那层确认要读 stdin,守护进程给它的 stdin
|
|
20
|
+
* 是 `ignore`,没人能回答那个提示。少了这个参数,子进程会死在确认提示上,而父进程
|
|
21
|
+
* 照样报「✓ 已启动」。(那个曾经「负责」这件事的 WEFLOW_ASSISTANT_DAEMON 环境变量
|
|
22
|
+
* 没有任何代码读它,而且它被当成了变量名里带 `=` 的键——已删。)
|
|
23
|
+
*
|
|
24
|
+
* 启动后**观察一小段时间再回报**:子进程立刻退出时(例如消息通道没登录),报出来的
|
|
25
|
+
* 是退出码与日志尾部,而不是一句假的成功。
|
|
26
|
+
*/
|
|
27
|
+
export declare function startDaemon(options?: StartDaemonOptions): Promise<{
|
|
11
28
|
started: boolean;
|
|
12
29
|
pid?: number;
|
|
13
30
|
error?: string;
|
|
14
|
-
}
|
|
31
|
+
}>;
|
|
15
32
|
export declare function stopDaemon(): {
|
|
16
33
|
stopped: boolean;
|
|
17
34
|
message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantDaemon.d.ts","sourceRoot":"","sources":["../../../src/services/assistantDaemon.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"assistantDaemon.d.ts","sourceRoot":"","sources":["../../../src/services/assistantDaemon.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,EAAqB,MAAM,eAAe,CAAA;AAYxD,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,aAAa,IAAI;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAWtE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK5C;AAED,4CAA4C;AAC5C,wBAAgB,iBAAiB,IAAI,IAAI,CAUxC;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAK,GAAG,MAAM,CAOtC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC;IAC3E,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAC/C,CAAC,CA4DD;AAED,wBAAgB,UAAU,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAelE"}
|